Merge branch 'develop' into main

This commit is contained in:
Thomas 2022-06-29 16:33:32 +02:00
commit 66c1b0c314
65 changed files with 1671 additions and 426 deletions

View File

@ -76,6 +76,7 @@ dependencies {
implementation "com.google.code.gson:gson:2.8.6"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.preference:preference:1.2.0'
implementation "org.conscrypt:conscrypt-android:2.5.2"

View File

@ -18,12 +18,14 @@
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:label="@string/app_name"
android:configChanges="orientation|screenSize"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppThemeDark"
>
<activity
android:name=".activities.MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
>
<intent-filter>

View File

@ -55,6 +55,7 @@ import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.navigation.NavController;
@ -746,12 +747,10 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
itemFilter.setTitle(show_filtered);
}
popup.setOnDismissListener(menu1 -> {
if (binding.viewPager.getAdapter() != null) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
});
String finalShow_filtered = show_filtered;
@ -838,8 +837,13 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
}
public void refreshFragment() {
if (binding.viewPager.getAdapter() != null) {
binding.viewPager.getAdapter().notifyDataSetChanged();
Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem());
if (fragment instanceof FragmentNotificationContainer) {
FragmentTransaction fragTransaction = getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragment).commit();
FragmentTransaction fragTransaction2 = getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragment);
fragTransaction2.commit();
}
}

View File

@ -125,7 +125,7 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface {
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(MediaActivity.this);
binding.mediaViewpager.setAdapter(mPagerAdapter);
binding.mediaViewpager.setSaveEnabled(false);
binding.mediaViewpager.setCurrentItem(mediaPosition - 1);
binding.haulerView.setOnDragDismissedListener(dragDirection -> ActivityCompat.finishAfterTransition(MediaActivity.this));
registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

View File

@ -59,12 +59,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.ArrayList;
import java.util.Date;
@ -249,34 +248,28 @@ public class ProfileActivity extends BaseActivity {
binding.accountTabLayout.removeAllTabs();
//Tablayout for timelines/following/followers
FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(ProfileActivity.this, account);
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count))));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count))));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count))));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab());
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab());
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab());
binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter);
binding.accountViewpager.setOffscreenPageLimit(3);
binding.accountViewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
binding.accountTabLayout.selectTab(binding.accountTabLayout.getTabAt(position));
}
});
binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
binding.accountViewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
new TabLayoutMediator(binding.accountTabLayout, binding.accountViewpager,
(tab, position) -> {
binding.accountViewpager.setCurrentItem(tab.getPosition(), true);
switch (position) {
case 0:
tab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)));
break;
case 1:
tab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)));
break;
case 2:
tab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)));
break;
}
}
).attach();
binding.accountTabLayout.setTabTextColors(ThemeHelper.getAttColor(ProfileActivity.this, R.attr.mTextColor), ContextCompat.getColor(ProfileActivity.this, R.color.cyanea_accent_dark_reference));
binding.accountTabLayout.setTabIconTint(ThemeHelper.getColorStateList(ProfileActivity.this));
boolean disableGif = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_GIF), false);

View File

@ -19,6 +19,7 @@ import static app.fedilab.android.helper.PinnedTimelineHelper.sortMenuItem;
import static app.fedilab.android.helper.PinnedTimelineHelper.sortPositionAsc;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
@ -37,6 +38,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -69,8 +71,10 @@ import app.fedilab.android.viewmodel.mastodon.ReorderVM;
import es.dmoral.toasty.Toasty;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@ -87,7 +91,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
private ActivityReorderTabsBinding binding;
private boolean changes;
private boolean bottomChanges;
private boolean update;
public void setChanges(boolean changes) {
this.changes = changes;
}
@ -112,10 +116,12 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
bottomChanges = false;
ReorderVM reorderVM = new ViewModelProvider(ReorderTimelinesActivity.this).get(ReorderVM.class);
reorderVM.getPinned().observe(ReorderTimelinesActivity.this, _pinned -> {
update = true;
this.pinned = _pinned;
if (this.pinned == null) {
this.pinned = new Pinned();
this.pinned.pinnedTimelines = new ArrayList<>();
update = false;
}
sortPositionAsc(this.pinned.pinnedTimelines);
reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this, ReorderTimelinesActivity.this);
@ -153,7 +159,6 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
} else if (item.getItemId() == R.id.action_add_timeline) {
addInstance();
}
return super.onOptionsItemSelected(item);
}
@ -168,11 +173,17 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ReorderTimelinesActivity.this, Helper.dialogStyle());
PopupSearchInstanceBinding popupSearchInstanceBinding = PopupSearchInstanceBinding.inflate(getLayoutInflater());
dialogBuilder.setView(popupSearchInstanceBinding.getRoot());
TextWatcher textWatcher = autoComplete(popupSearchInstanceBinding);
popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher);
popupSearchInstanceBinding.setAttachmentGroup.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId == R.id.twitter_accounts) {
popupSearchInstanceBinding.searchInstance.setHint(R.string.list_of_twitter_accounts);
popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher);
} else {
popupSearchInstanceBinding.searchInstance.setHint(R.string.instance);
popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher);
popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher);
}
});
popupSearchInstanceBinding.searchInstance.setFilters(new InputFilter[]{new InputFilter.LengthFilter(60)});
@ -180,16 +191,24 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
String instanceName = popupSearchInstanceBinding.searchInstance.getText().toString().trim().replace("@", "");
new Thread(() -> {
String url = null;
if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance)
boolean getCall = true;
RequestBody formBody = new FormBody.Builder()
.build();
if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) {
url = "https://" + instanceName + "/api/v1/timelines/public?local=true";
else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance)
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) {
url = "https://" + instanceName + "/api/v1/videos/";
else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) {
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) {
url = "https://" + instanceName + "/api/v1/timelines/public";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) {
url = "https://" + instanceName + "/api/notes/local-timeline";
getCall = false;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) {
url = "https://" + instanceName + "/api/statuses/public_timeline.json";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this);
String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
url = "https://" + nitterHost + "/" + instanceName.replaceAll("\\s", "") + "/rss";
}
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
@ -198,9 +217,16 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
.readTimeout(10, TimeUnit.SECONDS).build();
Request request;
if (url != null) {
request = new Request.Builder()
.url(url)
.build();
if (getCall) {
request = new Request.Builder()
.url(url)
.build();
} else {
request = new Request.Builder()
.url(url)
.post(formBody)
.build();
}
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
@ -236,13 +262,26 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE;
pinnedTimeline.position = pinned.pinnedTimelines.size();
pinned.pinnedTimelines.add(pinnedTimeline);
try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);
changes = true;
reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size());
} catch (DBException e) {
e.printStackTrace();
if (update) {
try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
} else {
try {
new Pinned(ReorderTimelinesActivity.this).insertPinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
}
reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size());
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD);
});
} else {
runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show());
@ -267,7 +306,11 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
popupSearchInstanceBinding.searchInstance.setOnItemClickListener((parent, view1, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim());
popupSearchInstanceBinding.searchInstance.addTextChangedListener(new TextWatcher() {
}
private TextWatcher autoComplete(PopupSearchInstanceBinding popupSearchInstanceBinding) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@ -314,8 +357,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra
}
}
}
});
};
}
@Override

View File

@ -24,7 +24,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityScheduledBinding;
@ -56,31 +56,31 @@ public class ScheduledActivity extends BaseActivity {
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
binding.title.setText(R.string.scheduled);
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_server)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_client)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.reblog)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleViewpager.setAdapter(new FedilabScheduledPageAdapter(ScheduledActivity.this));
binding.scheduleViewpager.setOffscreenPageLimit(3);
binding.scheduleTablayout.setTabTextColors(ThemeHelper.getAttColor(ScheduledActivity.this, R.attr.mTextColor), ContextCompat.getColor(ScheduledActivity.this, R.color.cyanea_accent_dark_reference));
binding.scheduleTablayout.setTabIconTint(ThemeHelper.getColorStateList(ScheduledActivity.this));
binding.scheduleTablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
binding.scheduleViewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
new TabLayoutMediator(binding.scheduleTablayout, binding.scheduleViewpager,
(tab, position) -> {
binding.scheduleViewpager.setCurrentItem(tab.getPosition(), true);
switch (position) {
case 0:
tab.setText(getString(R.string.toots_server));
break;
case 1:
tab.setText(getString(R.string.toots_client));
break;
case 2:
tab.setText(getString(R.string.reblog));
break;
}
}
).attach();
}
@Override

View File

@ -30,9 +30,9 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.jetbrains.annotations.NotNull;
@ -75,13 +75,35 @@ public class SearchResultTabActivity extends BaseActivity {
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
setTitle(search);
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.tags)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.accounts)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.toots)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.action_cache)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.setTabTextColors(ThemeHelper.getAttColor(SearchResultTabActivity.this, R.attr.mTextColor), ContextCompat.getColor(SearchResultTabActivity.this, R.color.cyanea_accent_dark_reference));
binding.searchTabLayout.setTabIconTint(ThemeHelper.getColorStateList(SearchResultTabActivity.this));
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this);
binding.searchViewpager.setAdapter(mPagerAdapter);
binding.searchViewpager.setSaveEnabled(false);
binding.searchViewpager.setOffscreenPageLimit(3);
new TabLayoutMediator(binding.searchTabLayout, binding.searchViewpager,
(tab, position) -> {
binding.searchViewpager.setCurrentItem(tab.getPosition(), true);
switch (position) {
case 0:
tab.setText(getString(R.string.tags));
break;
case 1:
tab.setText(getString(R.string.accounts));
break;
case 2:
tab.setText(getString(R.string.toots));
break;
case 3:
tab.setText(getString(R.string.action_cache));
break;
}
}
).attach();
binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@ -95,19 +117,16 @@ public class SearchResultTabActivity extends BaseActivity {
@Override
public void onTabReselected(TabLayout.Tab tab) {
Fragment fragment;
if (binding.searchViewpager.getAdapter() != null) {
fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("f" + binding.searchViewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonAccount) {
FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment);
fragmentMastodonAccount.scrollToTop();
} else if (fragment instanceof FragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop();
} else if (fragment instanceof FragmentMastodonTag) {
FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment);
fragmentMastodonTag.scrollToTop();
}
Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.searchViewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonAccount) {
FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment);
fragmentMastodonAccount.scrollToTop();
} else if (fragment instanceof FragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop();
} else if (fragment instanceof FragmentMastodonTag) {
FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment);
fragmentMastodonTag.scrollToTop();
}
}
});
@ -122,8 +141,6 @@ public class SearchResultTabActivity extends BaseActivity {
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
@ -156,29 +173,7 @@ public class SearchResultTabActivity extends BaseActivity {
});
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this);
binding.searchViewpager.setAdapter(mPagerAdapter);
binding.searchViewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
binding.searchTabLayout.selectTab(binding.searchTabLayout.getTabAt(position));
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
TabLayout.Tab tab = binding.searchTabLayout.getTabAt(position);
if (tab != null)
tab.select();
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
return true;
}

View File

@ -21,12 +21,17 @@ import app.fedilab.android.client.entities.api.Conversation;
import app.fedilab.android.client.entities.api.Marker;
import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.misskey.MisskeyNote;
import app.fedilab.android.client.entities.nitter.Nitter;
import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@ -191,4 +196,48 @@ public interface MastodonTimelinesService {
@Field("home[last_read_id]") String home_last_read_id,
@Field("notifications[last_read_id]") String notifications_last_read_id
);
@Headers({"Accept: application/json"})
@POST("api/notes")
Call<List<MisskeyNote>> getMisskey(@Body MisskeyNote.MisskeyParams params);
//Public timelines for Misskey
@FormUrlEncoded
@POST("api/notes")
Call<List<MisskeyNote>> getMisskey(
@Field("local") boolean local,
@Field("file") boolean file,
@Field("poll") boolean poll,
@Field("remote") boolean remote,
@Field("reply") boolean reply,
@Field("untilId") String max_id,
@Field("since_id") String since_id,
@Field("limit") Integer limit
);
@GET("api/v1/videos")
Call<PeertubeVideo> getPeertube(
@Query("start") String start,
@Query("filter") String filter,
@Query("sort") String sort,
@Query("count") int count
);
@GET("{names}/rss")
Call<Nitter> getNitter(
@Path("names") String id,
@Query("max_position") String max_position
);
@GET("{account}/rss")
Call<Nitter> getNitterAccount(
@Path("account") String account
);
@GET("api/v1/videos/{id}")
Call<PeertubeVideo.Video> getPeertubeVideo(
@Path("id") String id
);
}

View File

@ -44,4 +44,6 @@ public class Attachment implements Serializable {
@SerializedName("local_path")
public String local_path;
public String peertubeHost = null;
public String peertubeId = null;
}

View File

@ -277,7 +277,7 @@ public class Account extends BaseAccount implements Serializable {
throw new DBException("db is null. Wrong initialization.");
}
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_API + " = 'MASTODON'", null, null, null, null, null);
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, null, null, null, null, null, null);
return cursorToListUser(c);
} catch (Exception e) {
return null;

View File

@ -0,0 +1,150 @@
package app.fedilab.android.client.entities.misskey;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Status;
@SuppressWarnings("ALL")
public class MisskeyNote implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("replyId")
public String replyId;
@SerializedName("cw")
public String cw;
@SerializedName("text")
public String text;
@SerializedName("url")
public String url;
@SerializedName("uri")
public String uri;
@SerializedName("visibility")
public String visibility;
@SerializedName("repliesCount")
public int repliesCount;
@SerializedName("user")
public MisskeyUser user;
@SerializedName("files")
public List<MisskeyFile> files;
@SerializedName("emojis")
public List<MisskeyEmoji> emojis;
public static Status convert(MisskeyNote misskeyNote) {
Status status = new Status();
status.id = misskeyNote.id;
status.in_reply_to_id = misskeyNote.replyId;
status.content = misskeyNote.text != null ? misskeyNote.text : "";
status.text = misskeyNote.text != null ? misskeyNote.text : "";
status.spoiler_text = misskeyNote.cw;
status.visibility = misskeyNote.visibility;
status.created_at = misskeyNote.createdAt;
status.uri = misskeyNote.uri;
status.url = misskeyNote.url;
Account account = new Account();
account.id = misskeyNote.user.id;
account.acct = misskeyNote.user.username;
account.username = misskeyNote.user.username;
account.display_name = misskeyNote.user.name;
account.avatar = misskeyNote.user.avatarUrl;
account.avatar_static = misskeyNote.user.avatarUrl;
status.account = account;
if (misskeyNote.files != null && misskeyNote.files.size() > 0) {
List<Attachment> attachmentList = new ArrayList<>();
for (MisskeyFile misskeyFile : misskeyNote.files) {
Attachment attachment = new Attachment();
attachment.type = misskeyFile.type;
attachment.description = misskeyFile.comment;
attachment.url = misskeyFile.url;
attachment.preview_url = misskeyFile.thumbnailUrl;
if (misskeyFile.isSensitive) {
status.sensitive = true;
}
attachmentList.add(attachment);
}
status.media_attachments = attachmentList;
}
return status;
}
public static class MisskeyUser implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("username")
public String username;
@SerializedName("avatarUrl")
public String avatarUrl;
@SerializedName("emojis")
public List<MisskeyEmoji> emojis;
}
public static class MisskeyFile implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("comment")
public String comment;
@SerializedName("isSensitive")
public boolean isSensitive;
@SerializedName("thumbnailUrl")
public String thumbnailUrl;
@SerializedName("url")
public String url;
@SerializedName("type")
public String type;
}
public static class MisskeyEmoji implements Serializable {
@SerializedName("name")
public String name;
@SerializedName("comment")
public String url;
}
public static class MisskeyParams implements Serializable {
@SerializedName("local")
public boolean local = true;
@SerializedName("file")
public boolean file = false;
@SerializedName("poll")
public boolean poll = false;
@SerializedName("remote")
public boolean remote = false;
@SerializedName("reply")
public boolean reply = false;
@SerializedName("untilId")
public String untilId;
@SerializedName("limit")
public int limit;
}
}

View File

@ -0,0 +1,170 @@
package app.fedilab.android.client.entities.nitter;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import androidx.annotation.NonNull;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.Path;
import org.simpleframework.xml.Root;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.client.endpoints.MastodonTimelinesService;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.helper.Helper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
@Root(name = "rss", strict = false)
public class Nitter implements Serializable {
public static HashMap<String, Nitter> accounts = new HashMap<>();
@Element(name = "title")
@Path("channel")
public String title;
@Element(name = "image")
@Path("channel")
public Image image;
@ElementList(name = "item", inline = true)
@Path("channel")
public List<FeedItem> mFeedItems;
public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient)
.build();
return retrofit.create(MastodonTimelinesService.class);
}
public static Status convert(Context context, String instance, FeedItem feedItem) {
Status status = new Status();
status.id = feedItem.pubDate;
status.content = feedItem.description;
status.text = feedItem.title;
status.visibility = "public";
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz");
status.uri = feedItem.guid;
status.url = feedItem.link;
if (!accounts.containsValue(feedItem.creator)) {
MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance);
Call<Nitter> accountCall = mastodonTimelinesService.getNitterAccount(feedItem.creator.replace("@", ""));
if (accountCall != null) {
try {
Response<Nitter> publicTlResponse = accountCall.execute();
if (publicTlResponse.isSuccessful()) {
Nitter nitterAccount = publicTlResponse.body();
accounts.put(feedItem.creator, nitterAccount);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Nitter nitterAccount = accounts.get(feedItem.creator);
if (nitterAccount != null) {
app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account();
String[] names = nitterAccount.image.title.split("/");
account.id = feedItem.guid;
account.acct = names[1].replace("@", "");
account.username = names[1].replace("@", "");
account.display_name = names[0];
account.avatar = nitterAccount.image.url;
account.avatar_static = nitterAccount.image.url;
account.url = nitterAccount.image.link;
status.account = account;
}
if (feedItem.description != null) {
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
Matcher matcher = imgPattern.matcher(feedItem.description);
String description = feedItem.description;
ArrayList<Attachment> attachmentList = new ArrayList<>();
while (matcher.find()) {
description = description.replaceAll(Pattern.quote(matcher.group()), "");
Attachment attachment = new Attachment();
attachment.type = "image";
attachment.url = matcher.group(1);
attachment.preview_url = matcher.group(1);
attachment.id = matcher.group(1);
attachmentList.add(attachment);
}
status.media_attachments = attachmentList;
}
return status;
}
@Root(name = "image", strict = false)
public static class Image implements Serializable {
@Element(name = "title")
public String title;
@Element(name = "url")
public String url;
@Element(name = "link")
public String link;
}
@Root(name = "item", strict = false)
public static class FeedItem implements Serializable {
@Namespace(prefix = "dc")
@Element(name = "creator", required = false)
public String creator;
@Element(name = "title", required = false)
public String title;
@Element(name = "description", required = false)
public String description;
@Element(name = "pubDate", required = false)
public String pubDate;
@Element(name = "guid", required = false)
public String guid;
@Element(name = "link", required = false)
public String link;
@NonNull
@Override
public String toString() {
return "creator: " + creator + "\r" + "title: " + title + "\r" + "description: "
+ description + "\r" + "pubDate: " + pubDate + "\r"
+ "guid: " + guid + "\r" + "link: " + link;
}
}
}

View File

@ -0,0 +1,211 @@
package app.fedilab.android.client.entities.peertube;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Status;
@SuppressWarnings("ALL")
public class PeertubeVideo implements Serializable {
@SerializedName("total")
public int total;
@SerializedName("data")
public List<Video> data;
public static Status convert(Video peertubeVideo) {
Status status = new Status();
status.id = peertubeVideo.id;
status.content = peertubeVideo.description != null ? peertubeVideo.description : "";
status.text = peertubeVideo.description;
status.visibility = "public";
status.created_at = peertubeVideo.publishedAt;
status.uri = peertubeVideo.uuid;
status.sensitive = peertubeVideo.nsfw;
status.url = "https://" + peertubeVideo.account.host + "/videos/watch/" + peertubeVideo.uuid;
Account account = new Account();
account.id = peertubeVideo.channel.id;
account.acct = peertubeVideo.channel.name;
account.username = peertubeVideo.channel.name;
account.display_name = peertubeVideo.channel.displayName;
if (peertubeVideo.channel.avatar != null) {
account.avatar = "https://" + peertubeVideo.account.host + peertubeVideo.channel.avatar.path;
account.avatar_static = "https://" + peertubeVideo.account.host + peertubeVideo.channel.avatar.path;
}
status.account = account;
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.type = "video";
attachment.url = "https://" + peertubeVideo.account.host + peertubeVideo.embedPath;
attachment.preview_url = "https://" + peertubeVideo.account.host + peertubeVideo.thumbnailPath;
attachment.peertubeId = peertubeVideo.uuid;
attachment.peertubeHost = peertubeVideo.account.host;
attachmentList.add(attachment);
status.media_attachments = attachmentList;
return status;
}
public static class Video implements Serializable {
@SerializedName("account")
public PeertubeAccount account;
@SerializedName("category")
public Item category;
@SerializedName("channel")
public Channel channel;
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("description")
public String description;
@SerializedName("duration")
public int duration;
@SerializedName("embedPath")
public String embedPath;
@SerializedName("id")
public String id;
@SerializedName("isLive")
public boolean isLive = false;
@SerializedName("url")
public String url;
@SerializedName("isLocal")
public boolean isLocal;
@SerializedName("language")
public ItemStr language;
@SerializedName("licence")
public Item licence;
@SerializedName("likes")
public int likes;
@SerializedName("name")
public String name;
@SerializedName("nsfw")
public boolean nsfw;
@SerializedName("originallyPublishedAt")
public Date originallyPublishedAt;
@SerializedName("previewPath")
public String previewPath;
@SerializedName("privacy")
public Item privacy;
@SerializedName("publishedAt")
public Date publishedAt;
@SerializedName("thumbnailPath")
public String thumbnailPath;
@SerializedName("updatedAt")
public Date updatedAt;
@SerializedName("uuid")
public String uuid;
@SerializedName("files")
public List<File> files;
@SerializedName("views")
public int views;
}
public class File implements Serializable {
@SerializedName("fileDownloadUrl")
public String fileDownloadUrl;
@SerializedName("fileUrl")
public String fileUrl;
@SerializedName("fps")
public int fps;
@SerializedName("magnetUri")
public String magnetUri;
@SerializedName("metadataUrl")
public String metadataUrl;
@SerializedName("resolution")
public Item resolutions;
@SerializedName("size")
public long size;
@SerializedName("torrentDownloadUrl")
public String torrentDownloadUrl;
@SerializedName("torrentUrl")
public String torrentUrl;
}
public static class PeertubeAccount implements Serializable {
@SerializedName("avatar")
public Avatar avatar;
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("description")
public String description;
@SerializedName("displayName")
public String displayName;
@SerializedName("followersCount")
public int followersCount;
@SerializedName("followingCount")
public int followingCount;
@SerializedName("host")
public String host;
@SerializedName("hostRedundancyAllowed")
public boolean hostRedundancyAllowed;
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("username")
public String username;
@SerializedName("updatedAt")
public Date updatedAt;
@SerializedName("url")
public String url;
@SerializedName("userId")
public String userId;
}
public static class Item implements Serializable {
@SerializedName("id")
public int id;
@SerializedName("label")
public String label;
}
public static class ItemStr implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("label")
public String label;
}
public static class Channel implements Serializable {
@SerializedName("avatar")
public Avatar avatar;
@SerializedName("displayName")
public String displayName;
@SerializedName("host")
public String host;
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("url")
public String url;
}
public static class Avatar implements Serializable {
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("path")
public String path;
@SerializedName("updatedAt")
public Date updatedAt;
}
}

View File

@ -83,7 +83,7 @@ public class CrossActionHelper {
AlertDialog.Builder builderSingle = new AlertDialog.Builder(context, Helper.dialogStyle());
builderSingle.setTitle(context.getString(R.string.choose_accounts));
final AccountsSearchAdapter accountsSearchAdapter = new AccountsSearchAdapter(context, accountList);
final BaseAccount[] accountArray = new Account[accounts.size()];
final BaseAccount[] accountArray = new BaseAccount[accounts.size()];
int i = 0;
for (BaseAccount account : accounts) {
accountArray[i] = account;

View File

@ -280,6 +280,7 @@ public class Helper {
public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)");
public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)");
public static final Pattern xmppPattern = Pattern.compile("xmpp:[-a-zA-Z0-9+$&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
public static final Pattern peertubePattern = Pattern.compile("(https?://([\\da-z.-]+\\.[a-z.]{2,10}))/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$");
public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)");
public static final Pattern wikipediaPattern = Pattern.compile("([\\w_-]+)\\.wikipedia.org/(((?!([\"'<])).)*)");
public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)");
@ -589,6 +590,30 @@ public class Helper {
return date;
}
/**
* Convert String date from db to Date Object
*
* @param stringDate date to convert
* @return Date
*/
public static Date stringToDateWithFormat(Context context, String stringDate, String format) {
if (stringDate == null)
return null;
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format, userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
}
return date;
}
/**
* Converts dp to pixel
@ -625,8 +650,9 @@ public class Helper {
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://"))
if (url != null && !url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://")) {
url = "http://" + url;
}
intent.setData(Uri.parse(url));
try {
context.startActivity(intent);

View File

@ -213,19 +213,27 @@ public class MastodonHelper {
return;
}
String targetedUrl = disableGif ? (type == MediaAccountType.AVATAR ? account.avatar_static : account.header_static) : (type == MediaAccountType.AVATAR ? account.avatar : account.header);
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
Glide.with(view.getContext())
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
if (targetedUrl != null) {
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
Glide.with(view.getContext())
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
} else {
Glide.with(view.getContext())
.asGif()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
}
} else {
Glide.with(view.getContext())
.asGif()
.load(targetedUrl)
.asDrawable()
.load(placeholder)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
}
}

View File

@ -0,0 +1,113 @@
package app.fedilab.android.helper
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
*/
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && v !is ViewPager2) {
v = v.parent as? View
}
return v as? ViewPager2
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}

View File

@ -35,6 +35,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.ArrayList;
import java.util.Arrays;
@ -147,11 +148,14 @@ public class PinnedTimelineHelper {
activityMainBinding.tabLayout.removeAllTabs();
//Small hack to hide first tabs (they represent the item of the bottom menu)
int toRemove = itemToRemoveInBottomMenu(activity);
List<String> tabTitle = new ArrayList<>();
for (int i = 0; i < (BOTTOM_TIMELINE_COUNT - toRemove); i++) {
activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab());
tabTitle.add("");
((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE);
}
List<PinnedTimeline> pinnedTimelineVisibleList = new ArrayList<>();
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
if (pinnedTimeline.displayed) {
TabLayout.Tab tab = activityMainBinding.tabLayout.newTab();
@ -172,7 +176,7 @@ public class PinnedTimelineHelper {
}
TextView tv = (TextView) LayoutInflater.from(activity).inflate(R.layout.custom_tab_instance, new LinearLayout(activity), false);
tv.setText(name);
tabTitle.add(name);
tab.setCustomView(tv);
activityMainBinding.tabLayout.addTab(tab);
@ -205,16 +209,10 @@ public class PinnedTimelineHelper {
FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity, activity, pinned, bottomMenu);
activityMainBinding.viewPager.setAdapter(fedilabPageAdapter);
activityMainBinding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
activityMainBinding.tabLayout.selectTab(activityMainBinding.tabLayout.getTabAt(position));
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (position < BOTTOM_TIMELINE_COUNT - toRemove) {
activityMainBinding.bottomNavView.getMenu().getItem(position).setChecked(true);
} else {
@ -225,18 +223,14 @@ public class PinnedTimelineHelper {
activityMainBinding.bottomNavView.getMenu().setGroupCheckable(0, true, true);
}
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
new TabLayoutMediator(activityMainBinding.tabLayout, activityMainBinding.viewPager,
(tab, position) -> tab.setText(tabTitle.get(position))
).attach();
activityMainBinding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
activityMainBinding.viewPager.setCurrentItem(tab.getPosition());
}
@Override
@ -245,7 +239,7 @@ public class PinnedTimelineHelper {
@Override
public void onTabReselected(TabLayout.Tab tab) {
Fragment fragment = (Fragment) activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline) {
((FragmentMastodonTimeline) fragment).scrollToTop();
} else if (fragment instanceof FragmentMastodonConversation) {
@ -318,22 +312,19 @@ public class PinnedTimelineHelper {
popup.setOnDismissListener(menu1 -> {
if (changes[0]) {
FragmentMastodonTimeline fragmentMastodonTimeline;
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
}
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
}
}
});
@ -376,7 +367,7 @@ public class PinnedTimelineHelper {
}
} else if (itemId == R.id.action_any) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
LayoutInflater inflater = ((BaseMainActivity) activity).getLayoutInflater();
LayoutInflater inflater = activity.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.tags_any, new LinearLayout(activity), false);
dialogBuilder.setView(dialogView);
final EditText editText = dialogView.findViewById(R.id.filter_any);
@ -405,7 +396,7 @@ public class PinnedTimelineHelper {
View dialogView;
AlertDialog alertDialog;
dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
inflater = ((BaseMainActivity) activity).getLayoutInflater();
inflater = activity.getLayoutInflater();
dialogView = inflater.inflate(R.layout.tags_all, new LinearLayout(activity), false);
dialogBuilder.setView(dialogView);
final EditText editTextAll = dialogView.findViewById(R.id.filter_all);
@ -434,7 +425,7 @@ public class PinnedTimelineHelper {
View dialogView;
AlertDialog alertDialog;
dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
inflater = ((BaseMainActivity) activity).getLayoutInflater();
inflater = activity.getLayoutInflater();
dialogView = inflater.inflate(R.layout.tags_all, new LinearLayout(activity), false);
dialogBuilder.setView(dialogView);
final EditText editTextNone = dialogView.findViewById(R.id.filter_all);
@ -463,7 +454,7 @@ public class PinnedTimelineHelper {
View dialogView;
AlertDialog alertDialog;
dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
inflater = ((BaseMainActivity) activity).getLayoutInflater();
inflater = activity.getLayoutInflater();
dialogView = inflater.inflate(R.layout.tags_name, new LinearLayout(activity), false);
dialogBuilder.setView(dialogView);
final EditText editTextName = dialogView.findViewById(R.id.column_name);
@ -535,15 +526,13 @@ public class PinnedTimelineHelper {
});
changes[0] = true;
FragmentMastodonTimeline fragmentMastodonTimeline = null;
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
}
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
}
if (fragmentMastodonTimeline == null)
return false;
FragmentTransaction fragTransaction1 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction1 = activity.getSupportFragmentManager().beginTransaction();
pinned.pinnedTimelines.get(offSetPosition).remoteInstance.filteredWith = null;
remoteInstance.filteredWith = null;
@ -561,7 +550,7 @@ public class PinnedTimelineHelper {
bundle.putString("timelineId", remoteInstance.id);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.REMOTE);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
popup.getMenu().close();
@ -582,14 +571,12 @@ public class PinnedTimelineHelper {
MenuItem item = popup.getMenu().add(0, 0, Menu.NONE, title);
item.setOnMenuItemClickListener(item1 -> {
FragmentMastodonTimeline fragmentMastodonTimeline = null;
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
FragmentTransaction fragTransaction1 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction1 = activity.getSupportFragmentManager().beginTransaction();
if (fragmentMastodonTimeline == null)
return false;
pinned.pinnedTimelines.get(offSetPosition).remoteInstance.filteredWith = tag;
@ -608,7 +595,7 @@ public class PinnedTimelineHelper {
bundle.putString("currentfilter", remoteInstance.filteredWith);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.REMOTE);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
return false;
@ -634,7 +621,7 @@ public class PinnedTimelineHelper {
});
changes[0] = true;
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
LayoutInflater inflater = ((BaseMainActivity) activity).getLayoutInflater();
LayoutInflater inflater = activity.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.tags_instance, new LinearLayout(activity), false);
dialogBuilder.setView(dialogView);
final EditText editText = dialogView.findViewById(R.id.filter_words);
@ -665,14 +652,12 @@ public class PinnedTimelineHelper {
popup.setOnDismissListener(menu -> {
if (changes[0]) {
FragmentMastodonTimeline fragmentMastodonTimeline = null;
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("f" + activityMainBinding.viewPager.getCurrentItem());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
FragmentTransaction fragTransaction1 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction1 = activity.getSupportFragmentManager().beginTransaction();
if (fragmentMastodonTimeline == null)
return;
fragTransaction1.detach(fragmentMastodonTimeline).commit();
@ -685,7 +670,7 @@ public class PinnedTimelineHelper {
}
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.REMOTE);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = ((BaseMainActivity) activity).getSupportFragmentManager().beginTransaction();
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
}

View File

@ -247,7 +247,7 @@ public class SpannableHelper {
content.setSpan(new LongClickableSpan() {
@Override
public void onLongClick(View view) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(view.getContext(), Helper.dialogStyle());
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, Helper.dialogStyle());
PopupLinksBinding popupLinksBinding = PopupLinksBinding.inflate(LayoutInflater.from(context));
dialogBuilder.setView(popupLinksBinding.getRoot());
AlertDialog alertDialog = dialogBuilder.create();
@ -1007,6 +1007,41 @@ public class SpannableHelper {
return statuses;
}
public static List<Status> convertNitterStatus(Context context, List<Status> statuses) {
if (statuses != null) {
for (Status status : statuses) {
convertNitterStatus(context, status);
}
}
return statuses;
}
public static Status convertNitterStatus(Context context, Status status) {
if (status != null) {
status.span_content = SpannableHelper.convertNitter(context, status.content);
}
return status;
}
/**
* Convert HTML content to text. Also, it handles click on link and transform emoji
* This needs to be run asynchronously
*
* @param context {@link Context}
* @param text String - text to convert, it can be content, spoiler, poll items, etc.
* @return Spannable string
*/
private static Spannable convertNitter(@NonNull Context context, String text) {
SpannableString initialContent;
if (text == null) {
return null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
else
initialContent = new SpannableString(Html.fromHtml(text));
return initialContent;
}
public static List<Announcement> convertAnnouncement(Context context, List<Announcement> announcements) {
if (announcements != null) {

View File

@ -0,0 +1,44 @@
package app.fedilab.android.helper;
import android.view.View;
import androidx.viewpager2.widget.ViewPager2;
public class ZoomOutPageTransformer implements ViewPager2.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0f);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0f);
}
}
}

View File

@ -72,7 +72,7 @@ public class ContextAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
StatusAdapter.StatusViewHolder holder = (StatusAdapter.StatusViewHolder) viewHolder;
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, Timeline.TimeLineEnum.UNKNOWN, false);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, Timeline.TimeLineEnum.UNKNOWN, false, true);
//Hide/Show specific view
}

View File

@ -169,7 +169,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notificationList, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false);
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notificationList, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false, true);
holderStatus.bindingNotification.status.dateShort.setText(Helper.dateDiff(context, notification.created_at));
holderStatus.bindingNotification.containerTransparent.setAlpha(.3f);
if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS) {

View File

@ -141,11 +141,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private final Timeline.TimeLineEnum timelineType;
public FetchMoreCallBack fetchMoreCallBack;
private Context context;
private final boolean canBeFederated;
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified) {
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated) {
this.statusList = statuses;
this.timelineType = timelineType;
this.minified = minified;
this.canBeFederated = canBeFederated;
}
@ -293,7 +295,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
List<Notification> notificationList,
Status status,
Timeline.TimeLineEnum timelineType,
boolean minified) {
boolean minified, boolean canBeFederated) {
if (status == null) {
return;
}
@ -433,6 +435,24 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.statusContent.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
holder.binding.spoiler.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
}
//If the message contain a link to peertube and no media was added, we add it
if (statusToDeal.card != null && statusToDeal.card.url != null && (statusToDeal.media_attachments == null || statusToDeal.media_attachments.size() == 0)) {
Matcher matcherLink = Helper.peertubePattern.matcher(statusToDeal.card.url);
if (matcherLink.find()) { //Peertubee video
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.type = "video";
attachment.url = matcherLink.group(0);
attachment.preview_url = statusToDeal.card.image;
attachment.peertubeHost = matcherLink.group(2);
attachment.peertubeId = matcherLink.group(3);
attachmentList.add(attachment);
statusToDeal.media_attachments = attachmentList;
//adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
}
}
if (status.card != null && (display_card || status.isFocused)) {
if (status.card.width > status.card.height) {
holder.binding.cardImageHorizontal.setVisibility(View.VISIBLE);
@ -451,7 +471,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
holder.binding.card.setVisibility(View.GONE);
}
if (minified) {
if (minified || !canBeFederated) {
holder.binding.actionButtons.setVisibility(View.GONE);
} else {
holder.binding.actionButtons.setVisibility(View.VISIBLE);
@ -1280,7 +1300,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
return false;
});
if (!minified) {
if (!minified && canBeFederated) {
holder.binding.mainContainer.setOnClickListener(v -> {
holder.binding.statusContent.callOnClick();
});
@ -1317,6 +1337,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
}
});
} else if (!canBeFederated) {
holder.binding.mainContainer.setOnClickListener(v -> Helper.openBrowser(context, status.url));
holder.binding.statusContent.setOnClickListener(v -> Helper.openBrowser(context, status.url));
}
@ -1738,7 +1761,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, timelineType, minified);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, timelineType, minified, canBeFederated);
if (holder.timer != null) {
holder.timer.cancel();
holder.timer = null;

View File

@ -177,7 +177,7 @@ public class FragmentAdminAccount extends Fragment {
* @param adminAccounts AdminAccounts
*/
private void dealWithPagination(AdminAccounts adminAccounts) {
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loadingNextElements.setVisibility(View.GONE);

View File

@ -98,7 +98,7 @@ public class FragmentAdminReport extends Fragment {
* @param adminReports {@link AdminReports}
*/
private void initializeStatusesCommonView(final AdminReports adminReports) {
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loader.setVisibility(View.GONE);
@ -180,7 +180,7 @@ public class FragmentAdminReport extends Fragment {
*/
private void dealWithPagination(AdminReports admReports) {
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loadingNextElements.setVisibility(View.GONE);

View File

@ -31,6 +31,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide;
@ -51,6 +52,7 @@ import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.databinding.FragmentSlideMediaBinding;
import app.fedilab.android.helper.CacheDataSourceFactory;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.viewmodel.mastodon.TimelinesVM;
import app.fedilab.android.webview.CustomWebview;
import app.fedilab.android.webview.FedilabWebChromeClient;
import app.fedilab.android.webview.FedilabWebViewClient;
@ -198,32 +200,18 @@ public class FragmentMedia extends Fragment {
case "video":
case "audio":
case "gifv":
binding.pbarInf.setIndeterminate(false);
binding.pbarInf.setScaleY(3f);
binding.mediaVideo.setVisibility(View.VISIBLE);
Uri uri = Uri.parse(url);
String userAgent = sharedpreferences.getString(getString(R.string.SET_CUSTOM_USER_AGENT), Helper.USER_AGENT);
int video_cache = sharedpreferences.getInt(getString(R.string.SET_VIDEO_CACHE), Helper.DEFAULT_VIDEO_CACHE_MB);
ProgressiveMediaSource videoSource;
if (video_cache == 0) {
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(requireActivity(),
Util.getUserAgent(requireActivity(), userAgent), null);
videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri);
if (attachment.peertubeId != null) {
//It's a peertube video, we are fetching data
TimelinesVM timelinesVM = new ViewModelProvider(requireActivity()).get(TimelinesVM.class);
String finalType = type;
timelinesVM.getPeertubeVideo(attachment.peertubeHost, attachment.peertubeId).observe(requireActivity(), video -> {
if (video != null && video.files != null && video.files.size() > 0) {
loadVideo(video.files.get(0).fileUrl, finalType);
}
});
} else {
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(requireActivity());
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(uri);
loadVideo(url, type);
}
player = new SimpleExoPlayer.Builder(requireActivity()).build();
if (type.equalsIgnoreCase("gifv"))
player.setRepeatMode(Player.REPEAT_MODE_ONE);
binding.mediaVideo.setPlayer(player);
binding.loader.setVisibility(View.GONE);
binding.mediaPicture.setVisibility(View.GONE);
player.prepare(videoSource);
player.setPlayWhenReady(true);
break;
case "web":
binding.loader.setVisibility(View.GONE);
@ -261,6 +249,35 @@ public class FragmentMedia extends Fragment {
}
}
private void loadVideo(String url, String type) {
binding.pbarInf.setIndeterminate(false);
binding.pbarInf.setScaleY(3f);
binding.mediaVideo.setVisibility(View.VISIBLE);
Uri uri = Uri.parse(url);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
String userAgent = sharedpreferences.getString(getString(R.string.SET_CUSTOM_USER_AGENT), Helper.USER_AGENT);
int video_cache = sharedpreferences.getInt(getString(R.string.SET_VIDEO_CACHE), Helper.DEFAULT_VIDEO_CACHE_MB);
ProgressiveMediaSource videoSource;
if (video_cache == 0) {
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(requireActivity(),
Util.getUserAgent(requireActivity(), userAgent), null);
videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri);
} else {
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(requireActivity());
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(uri);
}
player = new SimpleExoPlayer.Builder(requireActivity()).build();
if (type.equalsIgnoreCase("gifv"))
player.setRepeatMode(Player.REPEAT_MODE_ONE);
binding.mediaVideo.setPlayer(player);
binding.loader.setVisibility(View.GONE);
binding.mediaPicture.setVisibility(View.GONE);
player.prepare(videoSource);
player.setPlayWhenReady(true);
}
@Override
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);

View File

@ -172,7 +172,7 @@ public class FragmentMastodonAccount extends Fragment {
*/
private void initializeAccountCommonView(final Accounts accounts) {
flagLoading = false;
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loader.setVisibility(View.GONE);
@ -242,7 +242,7 @@ public class FragmentMastodonAccount extends Fragment {
*/
private void dealWithPagination(Accounts fetched_accounts) {
flagLoading = false;
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loadingNextElements.setVisibility(View.GONE);

View File

@ -155,7 +155,7 @@ public class FragmentMastodonContext extends Fragment {
focusedStatus = (Status) getArguments().getSerializable(Helper.ARG_STATUS);
}
if (focusedStatus == null) {
requireActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
getChildFragmentManager().beginTransaction().remove(this).commit();
}
binding = FragmentPaginationBinding.inflate(inflater, container, false);
int c1 = getResources().getColor(R.color.cyanea_accent_reference);
@ -169,7 +169,7 @@ public class FragmentMastodonContext extends Fragment {
this.statuses = new ArrayList<>();
focusedStatus.isFocused = true;
this.statuses.add(focusedStatus);
statusAdapter = new StatusAdapter(this.statuses, Timeline.TimeLineEnum.UNKNOWN, false);
statusAdapter = new StatusAdapter(this.statuses, Timeline.TimeLineEnum.UNKNOWN, false, true);
binding.swipeContainer.setRefreshing(false);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity());
binding.recyclerView.setLayoutManager(mLayoutManager);
@ -227,6 +227,9 @@ public class FragmentMastodonContext extends Fragment {
Helper.sendToastMessage(requireActivity(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
return;
}
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
if (pullToRefresh) {
pullToRefresh = false;
int size = this.statuses.size();

View File

@ -192,7 +192,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
*/
private void initializeNotificationView(final Notifications notifications) {
flagLoading = false;
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loader.setVisibility(View.GONE);
@ -283,10 +283,10 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
* @param direction - DIRECTION null if first call, then is set to TOP or BOTTOM depending of scroll
*/
private void route(FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
new Thread(() -> {
if (binding == null) {
return;
}
QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(MainActivity.currentUserID, MainActivity.currentInstance, notificationType);
if (direction != FragmentMastodonTimeline.DIRECTION.REFRESH && !fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.notifications != null && quickLoad.notifications.size() > 0) {
Notifications notifications = new Notifications();
@ -365,10 +365,9 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
* @param fetched_notifications Notifications
*/
private synchronized void dealWithPagination(Notifications fetched_notifications, FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing) {
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
int currentPosition = mLayoutManager.findFirstVisibleItemPosition();
binding.swipeContainer.setRefreshing(false);
binding.loadingNextElements.setVisibility(View.GONE);
flagLoading = false;
@ -376,10 +375,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
flagLoading = fetched_notifications.pagination.max_id == null;
binding.noAction.setVisibility(View.GONE);
//Update the timeline with new statuses
int inserted = updateNotificationListWith(direction, fetched_notifications.notifications, fetchingMissing);
if (fetchingMissing) {
// binding.recyclerView.scrollToPosition(currentPosition + inserted);
}
updateNotificationListWith(direction, fetched_notifications.notifications, fetchingMissing);
if (!fetchingMissing) {
if (fetched_notifications.pagination.max_id == null) {
flagLoading = true;
@ -400,9 +396,8 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
*
* @param notificationsReceived - List<Notification> Notifications received
* @param fetchingMissing - boolean if the call concerns fetching messages (ie: refresh of from fetch more button)
* @return int - Number of messages that have been inserted in the middle of the timeline (ie between other statuses)
*/
private int updateNotificationListWith(FragmentMastodonTimeline.DIRECTION direction, List<Notification> notificationsReceived, boolean fetchingMissing) {
private void updateNotificationListWith(FragmentMastodonTimeline.DIRECTION direction, List<Notification> notificationsReceived, boolean fetchingMissing) {
int numberInserted = 0;
int lastInsertedPosition = 0;
int initialInsertedPosition = NOTIFICATION_PRESENT;
@ -438,7 +433,6 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
notificationAdapter.notifyItemInserted(insertAt);
}
}
return numberInserted;
}
/**

View File

@ -95,6 +95,9 @@ public class FragmentMastodonTag extends Fragment {
* @param tags List of {@link Tag}
*/
private void initializeTagCommonView(final List<Tag> tags) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loader.setVisibility(View.GONE);
binding.noAction.setVisibility(View.GONE);
binding.swipeContainer.setRefreshing(false);

View File

@ -21,6 +21,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -33,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -48,7 +50,9 @@ import app.fedilab.android.client.entities.api.Marker;
import app.fedilab.android.client.entities.api.Pagination;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.QuickLoad;
import app.fedilab.android.client.entities.app.RemoteInstance;
import app.fedilab.android.client.entities.app.TagTimeline;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.FragmentPaginationBinding;
@ -136,9 +140,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private Account accountTimeline;
private boolean exclude_replies, exclude_reblogs, show_pinned, media_only, minified;
private String viewModelKey, remoteInstance;
private PinnedTimeline pinnedTimeline;
private String ident;
private String instance, user_id;
private ArrayList<String> idOfAddedStatuses;
private boolean canBeFederated;
/**
* Return the position of the status in the ArrayList
@ -194,12 +200,23 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
timelineType = Timeline.TimeLineEnum.HOME;
instance = MainActivity.currentInstance;
user_id = MainActivity.currentUserID;
canBeFederated = true;
if (getArguments() != null) {
timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE);
list_id = getArguments().getString(Helper.ARG_LIST_ID, null);
search = getArguments().getString(Helper.ARG_SEARCH_KEYWORD, null);
searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null);
remoteInstance = getArguments().getString(Helper.ARG_REMOTE_INSTANCE, null);
pinnedTimeline = (PinnedTimeline) getArguments().getSerializable(Helper.ARG_REMOTE_INSTANCE);
if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) {
if (pinnedTimeline.remoteInstance.type != RemoteInstance.InstanceType.NITTER) {
remoteInstance = pinnedTimeline.remoteInstance.host;
} else {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
remoteInstance = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
canBeFederated = false;
}
}
tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE);
accountTimeline = (Account) getArguments().getSerializable(Helper.ARG_ACCOUNT);
exclude_replies = !getArguments().getBoolean(Helper.ARG_SHOW_REPLIES, true);
@ -260,7 +277,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
*/
private void initializeStatusesCommonView(final Statuses statuses, int position) {
flagLoading = false;
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.loader.setVisibility(View.GONE);
@ -322,12 +339,13 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
if (min_id == null || (statuses.pagination.min_id != null && statuses.pagination.min_id.compareTo(min_id) > 0)) {
min_id = statuses.pagination.min_id;
}
statusAdapter = new StatusAdapter(this.statuses, timelineType, minified);
statusAdapter = new StatusAdapter(this.statuses, timelineType, minified, canBeFederated);
statusAdapter.fetchMoreCallBack = this;
if (statusReport != null) {
scrollToTop();
}
mLayoutManager = new LinearLayoutManager(requireActivity());
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(statusAdapter);
@ -376,7 +394,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
* @param fetched_statuses Statuses
*/
private synchronized void dealWithPagination(Statuses fetched_statuses, DIRECTION direction, boolean fetchingMissing) {
if (binding == null) {
if (binding == null || !isAdded() || getActivity() == null) {
return;
}
binding.swipeContainer.setRefreshing(false);
@ -632,24 +650,91 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
});
}
} else if (timelineType == Timeline.TimeLineEnum.REMOTE) { //REMOTE TIMELINE
if (direction == null) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
//NITTER TIMELINES
if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) {
if (direction == null) {
timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, null)
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, max_id)
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, null)
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
} //GNU TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.GNU) {
}//MISSKEY TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.MISSKEY) {
if (direction == null) {
timelinesVM.getMisskey(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getMisskey(remoteInstance, max_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getMisskey(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
} //PEERTUBE TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) {
if (direction == null) {
timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPeertube(remoteInstance, String.valueOf(statuses.size()), MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
} else { //Other remote timelines
if (direction == null) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
}
} else if (timelineType == Timeline.TimeLineEnum.LIST) { //LIST TIMELINE
if (direction == null) {

View File

@ -33,6 +33,7 @@ import androidx.preference.PreferenceManager;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.ArrayList;
import java.util.List;
@ -60,19 +61,19 @@ public class FragmentNotificationContainer extends Fragment {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
boolean display_all_notification = sharedpreferences.getBoolean(getString(R.string.SET_DISPLAY_ALL_NOTIFICATIONS_TYPE) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, false);
if (!display_all_notification) {
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.all)));
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.mention)));
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.setTabMode(TabLayout.MODE_FIXED);
binding.viewpager.setAdapter(new FedilabNotificationPageAdapter(requireActivity(), false));
} else {
binding.tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.all)));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_reply_24));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_star_24));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_repeat));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_poll_24));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_home_24));
binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_person_add_alt_1_24));
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.tabLayout.addTab(binding.tabLayout.newTab());
binding.viewpager.setAdapter(new FedilabNotificationPageAdapter(requireActivity(), true));
}
AtomicBoolean changes = new AtomicBoolean(false);
@ -187,7 +188,6 @@ public class FragmentNotificationContainer extends Fragment {
}
});
dialogBuilder.setPositiveButton(R.string.close, (dialog, id) -> {
if (changes.get()) {
SharedPreferences.Editor editor = sharedpreferences.edit();
if (excludedCategoriesList.size() > 0) {
@ -202,7 +202,6 @@ public class FragmentNotificationContainer extends Fragment {
} else {
editor.putString(getString(R.string.SET_EXCLUDED_NOTIFICATIONS_TYPE) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, null);
}
editor.commit();
((BaseMainActivity) requireActivity()).refreshFragment();
}
@ -214,6 +213,47 @@ public class FragmentNotificationContainer extends Fragment {
binding.tabLayout.setTabTextColors(ThemeHelper.getAttColor(requireActivity(), R.attr.mTextColor), ContextCompat.getColor(requireActivity(), R.color.cyanea_accent_dark_reference));
binding.tabLayout.setTabIconTint(ThemeHelper.getColorStateList(requireActivity()));
new TabLayoutMediator(binding.tabLayout, binding.viewpager,
(tab, position) -> {
binding.viewpager.setCurrentItem(tab.getPosition(), true);
if (!display_all_notification) {
switch (position) {
case 0:
tab.setText(getString(R.string.all));
break;
case 1:
tab.setText(getString(R.string.mention));
break;
}
} else {
switch (position) {
case 0:
tab.setText(getString(R.string.all));
break;
case 1:
tab.setIcon(R.drawable.ic_baseline_reply_24);
break;
case 2:
tab.setIcon(R.drawable.ic_baseline_star_24);
break;
case 3:
tab.setIcon(R.drawable.ic_repeat);
break;
case 4:
tab.setIcon(R.drawable.ic_baseline_poll_24);
break;
case 5:
tab.setIcon(R.drawable.ic_baseline_home_24);
break;
case 6:
tab.setIcon(R.drawable.ic_baseline_person_add_alt_1_24);
break;
}
}
}
).attach();
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@ -227,15 +267,11 @@ public class FragmentNotificationContainer extends Fragment {
@Override
public void onTabReselected(TabLayout.Tab tab) {
Fragment fragment;
if (binding.viewpager.getAdapter() != null) {
fragment = (Fragment) requireActivity().getSupportFragmentManager().findFragmentByTag("f" + binding.viewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonNotification) {
FragmentMastodonNotification fragmentMastodonNotification = ((FragmentMastodonNotification) fragment);
fragmentMastodonNotification.scrollToTop();
}
Fragment fragment = getChildFragmentManager().findFragmentByTag("f" + binding.viewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonNotification) {
FragmentMastodonNotification fragmentMastodonNotification = ((FragmentMastodonNotification) fragment);
fragmentMastodonNotification.scrollToTop();
}
}
});
return binding.getRoot();
@ -244,9 +280,9 @@ public class FragmentNotificationContainer extends Fragment {
public void scrollToTop() {
if (binding != null) {
FragmentMastodonNotification fragmentMastodonNotification = (FragmentMastodonNotification) requireActivity().getSupportFragmentManager().findFragmentByTag("f" + binding.viewpager.getCurrentItem());
if (fragmentMastodonNotification != null) {
fragmentMastodonNotification.scrollToTop();
Fragment fragment = getChildFragmentManager().findFragmentByTag("f" + binding.viewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonNotification) {
((FragmentMastodonNotification) fragment).scrollToTop();
}
}
}

View File

@ -85,8 +85,7 @@ public class FedilabPageAdapter extends FragmentStateAdapter {
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.TAG) {
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, pinnedTimeline.tagTimeline);
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.REMOTE) {
String instance = pinnedTimeline.remoteInstance.host;
bundle.putString(Helper.ARG_REMOTE_INSTANCE, instance);
bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline);
}
}

View File

@ -27,6 +27,7 @@ import androidx.lifecycle.MutableLiveData;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -43,6 +44,9 @@ import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.BaseAccount;
import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.client.entities.misskey.MisskeyNote;
import app.fedilab.android.client.entities.nitter.Nitter;
import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
@ -53,6 +57,7 @@ import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
public class TimelinesVM extends AndroidViewModel {
@ -68,6 +73,7 @@ public class TimelinesVM extends AndroidViewModel {
private MutableLiveData<List<StatusDraft>> statusDraftListMutableLiveData;
private MutableLiveData<Status> statusMutableLiveData;
private MutableLiveData<Statuses> statusesMutableLiveData;
private MutableLiveData<PeertubeVideo.Video> peertubeVideoMutableLiveData;
private MutableLiveData<Conversations> conversationListMutableLiveData;
private MutableLiveData<MastodonList> mastodonListMutableLiveData;
private MutableLiveData<List<MastodonList>> mastodonListListMutableLiveData;
@ -77,6 +83,24 @@ public class TimelinesVM extends AndroidViewModel {
super(application);
}
private MastodonTimelinesService initInstanceOnly(String instance) {
Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
return retrofit.create(MastodonTimelinesService.class);
}
private MastodonTimelinesService initInstanceXMLOnly(String instance) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient)
.build();
return retrofit.create(MastodonTimelinesService.class);
}
private MastodonTimelinesService init(String instance) {
Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create();
@ -134,6 +158,172 @@ public class TimelinesVM extends AndroidViewModel {
}
/**
* Public timeline for Nitter
*
* @param max_position Return results older than this id
* @return {@link LiveData} containing a {@link Statuses}
*/
public LiveData<Statuses> getNitter(@NonNull String instance,
String accountsStr,
String max_position) {
MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(instance);
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
Call<Nitter> publicTlCall = mastodonTimelinesService.getNitter(accountsStr, max_position);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
Response<Nitter> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
Nitter rssResponse = publicTlResponse.body();
List<Status> statusList = new ArrayList<>();
if (rssResponse != null && rssResponse.mFeedItems != null) {
for (Nitter.FeedItem feedItem : rssResponse.mFeedItems) {
Status status = Nitter.convert(getApplication(), instance, feedItem);
statusList.add(status);
}
}
statuses.statuses = SpannableHelper.convertNitterStatus(getApplication().getApplicationContext(), statusList);
statuses.pagination = MastodonHelper.getPagination(publicTlResponse.headers());
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses);
mainHandler.post(myRunnable);
}).start();
return statusesMutableLiveData;
}
/**
* Public timeline for Misskey
*
* @param untilId Return results older than this id
* @param limit Maximum number of results to return. Defaults to 20.
* @return {@link LiveData} containing a {@link Statuses}
*/
public LiveData<Statuses> getMisskey(@NonNull String instance,
String untilId,
Integer limit) {
MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance);
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
MisskeyNote.MisskeyParams misskeyParams = new MisskeyNote.MisskeyParams();
misskeyParams.untilId = untilId;
misskeyParams.limit = limit;
Call<List<MisskeyNote>> publicTlCall = mastodonTimelinesService.getMisskey(misskeyParams);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
Response<List<MisskeyNote>> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
List<MisskeyNote> misskeyNoteList = publicTlResponse.body();
List<Status> statusList = new ArrayList<>();
if (misskeyNoteList != null) {
for (MisskeyNote misskeyNote : misskeyNoteList) {
Status status = MisskeyNote.convert(misskeyNote);
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
statuses.pagination.min_id = statusList.get(0).id;
statuses.pagination.max_id = statusList.get(statusList.size() - 1).id;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses);
mainHandler.post(myRunnable);
}).start();
return statusesMutableLiveData;
}
/**
* Public timeline for Peertube
*
* @param maxId Return results older than this id
* @param limit Maximum number of results to return. Defaults to 20.
* @return {@link LiveData} containing a {@link Statuses}
*/
public LiveData<Statuses> getPeertube(@NonNull String instance,
String maxId,
Integer limit) {
MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance);
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
Call<PeertubeVideo> publicTlCall = mastodonTimelinesService.getPeertube(maxId, "local", "-publishedAt", limit);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
Response<PeertubeVideo> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
PeertubeVideo peertubeVideo = publicTlResponse.body();
List<Status> statusList = new ArrayList<>();
if (peertubeVideo != null) {
for (PeertubeVideo.Video video : peertubeVideo.data) {
Status status = PeertubeVideo.convert(video);
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
//These values are not used.
statuses.pagination.min_id = null;
statuses.pagination.max_id = null;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses);
mainHandler.post(myRunnable);
}).start();
return statusesMutableLiveData;
}
/**
* Returns details for a peertube video
*
* @return {@link LiveData} containing a {@link PeertubeVideo.Video}
*/
public LiveData<PeertubeVideo.Video> getPeertubeVideo(@NonNull String instance, String id) {
MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance);
peertubeVideoMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
Call<PeertubeVideo.Video> publicTlCall = mastodonTimelinesService.getPeertubeVideo(id);
PeertubeVideo.Video peertubeVideo = null;
try {
Response<PeertubeVideo.Video> videoResponse = publicTlCall.execute();
if (videoResponse.isSuccessful()) {
peertubeVideo = videoResponse.body();
}
} catch (Exception e) {
e.printStackTrace();
}
Handler mainHandler = new Handler(Looper.getMainLooper());
PeertubeVideo.Video finalPeertubeVideo = peertubeVideo;
Runnable myRunnable = () -> peertubeVideoMutableLiveData.setValue(finalPeertubeVideo);
mainHandler.post(myRunnable);
}).start();
return peertubeVideoMutableLiveData;
}
/**
* View public statuses containing the given hashtag.
*

View File

@ -532,6 +532,14 @@
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.tabs.TabLayout
android:id="@+id/account_tabLayout"
android:layout_width="match_parent"
@ -539,13 +547,11 @@
android:background="?backgroundColorLight"
app:tabGravity="fill"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/account_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/account_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -30,9 +30,15 @@
app:tabIndicatorColor="@color/cyanea_accent_dark_reference"
app:tabMode="scrollable" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/search_viewpager"
<app.fedilab.android.helper.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
android:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/search_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</app.fedilab.android.helper.NestedScrollableHost>
>
</LinearLayout>

View File

@ -474,7 +474,9 @@ Ara ja pots connectar-te al compte escrivint <b>%1$s</b> en el primer camp i fen
<string name="set_enable_crash_report">Habilita els informes de fallida</string>
<string name="set_enable_crash_report_indication">Quan està habilitat, es crea un informe local de fallida que després pots compartir.</string>
<string name="crash_title">Malaslab s\'ha aturat :(</string>
<string name="crash_message">Pots enviar-me l\'informe de la fallada per email. Ajudarà a resoldre-ho :)\n\nPots afegir-hi contingut. Gràcies!</string>
<string name="crash_message">Pots enviar-me l\'informe de la fallada per correu electrònic. Ajudarà a resoldre-ho :)
\n
\nPots afegir-hi contingut. Gràcies!</string>
<string name="visibility">Visibilitat</string>
<string name="set_disable_animated_emoji">Deshabilita els emojis animats personalitzats</string>
<string name="report_account">Denuncia el compte</string>

View File

@ -17,15 +17,15 @@
<string name="password">Passwort</string>
<string name="email">E-Mail</string>
<string name="accounts">Konten</string>
<string name="toots">Toots</string>
<string name="toots">Nachrichten</string>
<string name="tags">Schlagwörter (tags)</string>
<string name="save">Speichern</string>
<string name="instance">Instanz</string>
<string name="instance_example">Instanz: mastodon.social</string>
<string name="toast_account_changed" formatted="false">Konto %1$s wird verwendet</string>
<string name="add_account">Konto hinzufügen</string>
<string name="clipboard">Der Inhalt des Toots wurde in die Zwischenablage kopiert</string>
<string name="clipboard_url">Die URL des Toots wurde in die Zwischenablage kopiert</string>
<string name="clipboard">Der Inhalt der Nachricht wurde in die Zwischenablage kopiert</string>
<string name="clipboard_url">Die URL der Nachricht wurde in die Zwischenablage kopiert</string>
<string name="camera">Kamera</string>
<string name="delete_all">Alles löschen</string>
<string name="schedule">Planen</string>
@ -57,17 +57,17 @@
<string name="follow_request">Folgeanfragen</string>
<string name="settings">Einstellungen</string>
<string name="send_email">E-Mail senden</string>
<string name="scheduled_toots">Geplante Toots</string>
<string name="scheduled_toots">Geplante Nachrichten</string>
<string name="disclaimer_full">Die folgenden Informationen könnten das Profil des Nutzers unvollständig wiedergeben.</string>
<string name="insert_emoji">Emoji einfügen</string>
<string name="no_emoji">Die App verfügt derzeit nicht über benutzerdefinierte Emojis.</string>
<string name="logout_account_confirmation">Sind Sie sicher, dass Sie @%1$s@%2$s abmelden möchten?</string>
<!-- Status -->
<string name="no_status">Kein Toot zum Anzeigen</string>
<string name="favourite_add">Diesen Toot deinen Favoriten hinzufügen?</string>
<string name="favourite_remove">Diesen Toot aus Ihren Favoriten entfernen?</string>
<string name="reblog_add">Teile diesen Toot?</string>
<string name="reblog_remove">Geteilten Toot zurückziehen?</string>
<string name="no_status">Keine Nachrichten zum Anzeigen</string>
<string name="favourite_add">Diese Nachricht deinen Favoriten hinzufügen\?</string>
<string name="favourite_remove">Diese Nachricht aus Ihren Favoriten entfernen\?</string>
<string name="reblog_add">Diese Nachricht teilen\?</string>
<string name="reblog_remove">Geteilte Nachricht zurückziehen\?</string>
<string name="more_action_1">Stummschalten</string>
<string name="more_action_2">Sperren</string>
<string name="more_action_3">Melden</string>
@ -122,8 +122,8 @@
<!-- TOOT -->
<string name="toot_select_image_error">Ein Fehler während der Auswahl ist aufgetreten!</string>
<string name="toot_delete_media">Diese Datei löschen?</string>
<string name="toot_error_no_content">Dein Toot ist leer!</string>
<string name="toot_sent">Der Toot wurde gesendet!</string>
<string name="toot_error_no_content">Deine Nachricht ist leer!</string>
<string name="toot_sent">Die Nachricht wurde gesendet!</string>
<string name="toot_sensitive">Vertrauliche Inhalte?</string>
<string name="no_draft">Keine Entwürfe vorhanden!</string>
<string name="choose_accounts">Konto auswählen</string>
@ -143,14 +143,15 @@
<!-- Accounts -->
<string name="no_accounts">Keinen Nutzer gefunden</string>
<string name="no_follow_request">Keine Anfragen zu Folgen</string>
<string name="status_cnt">Toots \n %1$s</string>
<string name="status_cnt">Nachrichten
\n %1$s</string>
<string name="following_cnt">Folgt \n %1$s</string>
<string name="followers_cnt">Folgende \n %1$s</string>
<string name="reject">Ablehnen</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Keine geplanten Toots vorhanden!</string>
<string name="remove_scheduled">Geplanten Toot löschen?</string>
<string name="toot_scheduled">Der Toot wurde geplant!</string>
<string name="no_scheduled_toots">Keine geplanten Nachrichten vorhanden!</string>
<string name="remove_scheduled">Geplante Nachricht löschen\?</string>
<string name="toot_scheduled">Die Nachricht wurde geplant!</string>
<string name="toot_scheduled_date">Der geplante Termin muss nach dem aktuellen Zeitpunkt liegen!</string>
<!-- timed mute -->
<string name="timed_mute_date_error">Die Dauer für den Lautlosmodus sollte mehr als eine Minute betragen.</string>
@ -176,10 +177,10 @@
<string name="toast_unmute">Lautlosmodus für dieses Konto aufgehoben!</string>
<string name="toast_follow">Du folgst dem Nutzer!</string>
<string name="toast_unfollow">Du folgst dem Nutzer nicht mehr!</string>
<string name="toast_reblog">Der Toot wurde geteilt!</string>
<string name="toast_unreblog">Der Toot wird nicht länger geteilt!</string>
<string name="toast_favourite">Der Toot wurde deinen Favoriten hinzugefügt!</string>
<string name="toast_unfavourite">Der Toot wurde aus deinen Favoriten entfernt!</string>
<string name="toast_reblog">Die Nachricht wurde geteilt!</string>
<string name="toast_unreblog">Die Nachricht wird nicht länger geteilt!</string>
<string name="toast_favourite">Die Nachricht wurde deinen Favoriten hinzugefügt!</string>
<string name="toast_unfavourite">Die Nachricht wurde aus deinen Favoriten entfernt!</string>
<string name="toast_error">Es ist ein Fehler aufgetreten!</string>
<string name="toast_code_error">Ein Fehler ist aufgetreten! Die Instanz hat keinen Autorisierungscode gesendet!</string>
<string name="toast_error_instance">Der Name der Instanz scheint ungültig zu sein!</string>
@ -188,7 +189,7 @@
<string name="nothing_to_do">Keine Aktion kann durchgeführt werden</string>
<string name="toast_error_translate">Während der Übersetzung ist ein Fehler aufgetreten!</string>
<!-- Settings -->
<string name="set_toots_page">Anzahl der Toots pro Ladevorgang</string>
<string name="set_toots_page">Anzahl der Nachrichten pro Ladevorgang</string>
<string name="set_disable_gif">GIF Avatare deaktivieren</string>
<string name="set_notif_follow">mir jemand folgt</string>
<string name="set_notif_follow_share">jemand meinen Beitrag teilt</string>
@ -267,7 +268,7 @@
<string name="poxy_port">Port</string>
<string name="poxy_login">Login</string>
<string name="poxy_password">Passwort</string>
<string name="set_share_details">Toot Details beim Teilen hinzufügen</string>
<string name="set_share_details">Nachricht-Details beim Teilen hinzufügen</string>
<string name="support_the_app_on_liberapay">Unterstütze die app auf Liberapay</string>
<string name="alert_regex">Es gibt einen Fehler im regulären Ausdruck!</string>
<string name="toast_instance_unavailable">Es wurden keine Zeitleisten in dieser Instanz gefunden!</string>
@ -286,9 +287,9 @@
<string name="context_public">Öffentliche Zeitleiste</string>
<string name="context_notification">Benachrichtigungen</string>
<string name="context_conversation">Unterhaltungen</string>
<string name="filter_keyword_explanations">Wird unabhängig vom umgebenen Text oder Inhaltswarnung eines Beitrags verglichen</string>
<string name="filter_keyword_explanations">Wird unabhängig vom umgebenen Text oder Inhaltswarnung einer Nachricht verglichen</string>
<string name="context_drop">Entfernen anstatt zu verstecken</string>
<string name="context_drop_explanations">Gefilterte Beiträge werden unwiderruflich gefiltert, selbst wenn der Filter später entfernt wurde</string>
<string name="context_drop_explanations">Gefilterte Nachrichten werden unwiderruflich verschwinden, selbst wenn der Filter später entfernt wurde</string>
<string name="context_whole_word_explanations">Wenn das Schlüsselwort oder -phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet werden, wenn es dem ganzen Wort entspricht</string>
<string name="context_whole_word">Ganzes Wort</string>
<string name="filter_context">Kontext filtern</string>
@ -303,7 +304,7 @@
<string name="channel_notif_fav">Neuer Favorit</string>
<string name="channel_notif_mention">Neue Erwähnung</string>
<string name="channel_notif_poll">Umfrage beendet</string>
<string name="channel_notif_backup">Toots Sicherung</string>
<string name="channel_notif_backup">Nachrichten-Sicherung</string>
<string name="channel_notif_status">Neue Beiträge</string>
<string name="channel_notif_media">Medien Download</string>
<string name="select_sound">Klingelton auswählen</string>
@ -315,11 +316,11 @@
<string name="peertube_instance">Peertube Instanz</string>
<string name="set_display_emoji">Emoji One verwenden</string>
<string name="information">Information</string>
<string name="set_display_card">Vorschau in allen Toots anzeigen</string>
<string name="set_display_card">Vorschau in allen Nachrichten anzeigen</string>
<string name="account_id_clipbloard">Konto-ID wurde in die Zwischenablage kopiert!</string>
<string name="set_change_locale">Sprache ändern</string>
<string name="truncate_long_toots">Lange Toots kürzen</string>
<string name="set_truncate_toot">Kürze Toots mit mehr als \'x\' Zeilen. Null bedeutet deaktiviert.</string>
<string name="truncate_long_toots">Lange Nachrichten kürzen</string>
<string name="set_truncate_toot">Kürze Nachrichten mit mehr als x Zeilen. Null bedeutet deaktiviert.</string>
<string name="display_toot_truncate">Mehr anzeigen</string>
<string name="hide_toot_truncate">Weniger anzeigen</string>
<string name="tags_already_stored">Dieses Schlagwort existiert bereits!</string>
@ -365,8 +366,8 @@
<string name="category">Kategorie</string>
<string name="description">Beschreibung</string>
<string name="share">Teilen</string>
<string name="toots_server">Toots (Server)</string>
<string name="toots_client">Toots (Gerät)</string>
<string name="toots_server">Nachrichten (Server)</string>
<string name="toots_client">Nachrichten (Gerät)</string>
<string name="settings_category_label_timelines">Zeitleisten</string>
<string name="settings_category_label_interface">Benutzeroberfläche</string>
<string name="contact">Kontakte</string>
@ -388,7 +389,7 @@
<string name="poll_finish_at">endet um %s</string>
<string name="vote">Abstimmen</string>
<string name="notif_poll">Eine Umfrage, in der du abgestimmt hast, ist beendet</string>
<string name="notif_poll_self">Eine Ihrer Umfragen ist beendet</string>
<string name="notif_poll_self">Eine Ihrer Umfragen wurde beendet</string>
<string name="settings_category_notif_categories">Kategorien</string>
<string name="move_timeline">Zeitleiste verschieben</string>
<string name="hide_timeline">Zeitleiste ausblenden</string>
@ -441,7 +442,7 @@
<string name="password_indicator">Mindestens 8 Zeichen verwenden</string>
<string name="password_too_short">Das Passwort muss mindestens 8 Zeichen lang sein</string>
<string name="username_error">Der Benutzername darf nur Buchstaben, Ziffern und Unterstriche enthalten</string>
<string name="account_created">Account erstellt!</string>
<string name="account_created">Konto erstellt!</string>
<string name="account_created_message"> Dein Konto wurde erfolgreich erstellt!
\n
\nDenke daran, deine E-Mail-Adresse innerhalb der nächsten 48 Stunden zu bestätigen.
@ -533,7 +534,7 @@
<string name="text_color_title">Textfarbe</string>
<string name="text_color">Ändern Sie die Textfarbe in posts</string>
<string name="pref_custom_theme">Verwenden Sie ein benutzerdefiniertes Design</string>
<string name="theming">Gestaltung</string>
<string name="theming">Farbschema</string>
<string name="data_export_theme">Das Theme wurde exportiert</string>
<string name="data_export_theme_success">Das Design wurde erfolgreich als CSV exportiert</string>
<string name="import_theme">Theme importieren</string>
@ -585,8 +586,8 @@
<string name="stop_recording">Aufnahme anhalten</string>
<string name="report_val1">Ich mag es nicht</string>
<string name="report_val2">Es ist Spam</string>
<string name="toast_bookmark">Der Tröt wurde zu deinen Lesezeichen hinzugefügt!</string>
<string name="toast_unbookmark">Der Tröt wurde von deinen Lesezeichen entfernt!</string>
<string name="toast_bookmark">Die Nachricht wurde zu deinen Lesezeichen hinzugefügt!</string>
<string name="toast_unbookmark">Die Nachricht wurde von deinen Lesezeichen entfernt!</string>
<string name="set_accounts_page">Anzahl der Konten pro Laden</string>
<string name="category_music">Musik</string>
<string name="cannot_be_empty">Dieses Feld kann nicht leer sein!</string>
@ -613,12 +614,12 @@
<string name="report_all_more">Wähle alle zutreffenden Punkte aus</string>
<string name="report_3_title">Welche Regeln werden verletzt\?</string>
<string name="report_2_title">Gibt es Beiträge, die diesen Bericht belegen\?</string>
<string name="dont_have_an_account">Noch kein Account vorhanden\?</string>
<string name="dont_have_an_account">Noch kein Konto vorhanden\?</string>
<string name="report_sent">Bericht wurde gesendet!</string>
<string name="report_more_forward">Weiterleiten an %1$s</string>
<string name="join_the_fediverse">Komm ins Fediverse</string>
<string name="notif_display_mentions">Erwähnungen</string>
<string name="about_mastodon">\"Mastodon ist nicht wie Twitter oder Facebook, es besteht aus einem Netzwerk von tausenden, durch unterschiedliche Organisationen und Einzelpersonen betriebene, Communities, die ein nahtloses Social Media Erlebnis bieten.\"</string>
<string name="about_mastodon">Mastodon ist nicht wie Twitter oder Facebook, es besteht aus einem Netzwerk von tausenden, durch unterschiedliche Organisationen und Einzelpersonen betriebene, Gemeinschaften, die ein nahtloses Soziale-Medien-Erlebnis bieten.“</string>
<string name="notif_display_favourites">Favoriten</string>
<string name="save_changes">Änderungen speichern</string>
<string name="locked">Gesperrt</string>
@ -633,7 +634,7 @@
<string name="scheduled">Geplant</string>
<string name="profiled_updated">Profil wurde aktualisiert!</string>
<string name="not_valid_list_name">Listenname ist nicht gültig!</string>
<string name="no_account_in_list">Keine Accounts für diese Liste gefunden!</string>
<string name="no_account_in_list">Keine Konten für diese Liste gefunden!</string>
<string name="delete_field_confirm">Bist du sicher, dass du dieses Feld löschen willst\?</string>
<string name="delete_field">Feld löschen</string>
<string name="type_of_notifications">Art der Benachrichtigungen</string>
@ -663,14 +664,14 @@
<string name="report_indication_title_status">Sage uns, was es mit diesem Beitrag auf sich hat</string>
<string name="report_1_mute_title">Stummschalten %1$s</string>
<string name="report_1_block_title">Blockieren %1$s</string>
<string name="invite_join_the_fediverse">Hallo! Wir laden Dich ein, dem Fediverse beizutreten.</string>
<string name="set_bot_content">Bot-Account</string>
<string name="invite_join_the_fediverse">Hallo! Wir laden dich ein, dem Fediverse beizutreten.</string>
<string name="set_bot_content">Bot-Konto</string>
<string name="interactions">Interaktionen</string>
<string name="set_discoverable_content">Konto auffindbar</string>
<string name="pref_custom_theme_new_summary">Erlaubt die Erstellung des eigenen Themas</string>
<string name="pref_theme_base_summary">Wähle, ob die Basis des Themas dunkel oder hell sein soll</string>
<string name="types_of_notifications_to_display">Arten der anzuzeigenden Benachrichtigungen</string>
<string name="toots_visibility_title">Standardmäßige Sichtbarkeit der Toots:</string>
<string name="toots_visibility_title">Standardmäßige Sichtbarkeit der Nachrichten:</string>
<string name="set_notifications_page">Anzahl an Benachrichtigungen pro Ladezyklus</string>
<string name="replace_instagram_description">Nutze eine alternative Benutzeroberfläche für Instagram</string>
<string name="replace_instagram_host">Instagram Frontend Domain</string>
@ -701,4 +702,22 @@
<string name="filter">Filter</string>
<string name="approve">Genehmigen</string>
<string name="status">Status</string>
<string name="tap_here_to_refresh_poll">Hier tippen, um die Umfrage zu aktualisieren</string>
<string name="label_line">Linie</string>
<string name="label_rectangle">Eckig</string>
<string name="files_cache_size">Datei-Cache Größe</string>
<string name="fetch_more_messages">Mehr Nachrichten laden…</string>
<string name="messages_stored_in_drafts">Nachrichten in den Entwürfen gespeichert</string>
<string name="label_oval">Rund</string>
<string name="label_eraser_mode">Radier-Modus</string>
<string name="action_announcement_from_to">Ankündigung · %1$s - %2$s</string>
<string name="delete_cache">Cache leeren</string>
<string name="messages_in_cache_for_home">Nachrichten im Cache für Startseite</string>
<string name="messages_in_cache_for_other_timelines">Nachrichten im Cache für andere Timelines</string>
<string name="clear_cache">Cache leeren</string>
<string name="delete_cache_message">Sind Sie sich sicher, den Cache zu leeren\? Wenn Sie Entwürfe mit Bildern, etc haben, werden die angehängten Dateien gelöscht.</string>
<string name="msg_save_image">Möchten Sie ohne das Bild zu speichern verlassen\?</string>
<string name="label_shape">Form</string>
<string name="domain">Domain</string>
<string name="staff">Personal</string>
</resources>

View File

@ -15,7 +15,7 @@
<string name="save_over">Archivos multimedia guardados</string>
<string name="download_from" formatted="false">Archivo: %1$s</string>
<string name="password">Contraseña</string>
<string name="email">Correo Electrónico</string>
<string name="email">Correo electrónico</string>
<string name="accounts">Cuentas</string>
<string name="toots">Toots</string>
<string name="tags">Etiquetas</string>

View File

@ -15,7 +15,7 @@
<string name="save_over">Enregistrement terminé</string>
<string name="download_from" formatted="false">Fichier : %1$s</string>
<string name="password">Mot de passe</string>
<string name="email">Email</string>
<string name="email">Courriel</string>
<string name="accounts">Comptes</string>
<string name="toots">Messages</string>
<string name="tags">Étiquettes</string>
@ -56,18 +56,18 @@
<string name="notifications">Notifications</string>
<string name="follow_request">Demandes dabonnements</string>
<string name="settings">Paramètres</string>
<string name="send_email">Envoyer un Email</string>
<string name="send_email">Envoyer un courriel</string>
<string name="scheduled_toots">Messages programmés</string>
<string name="disclaimer_full">Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.</string>
<string name="insert_emoji">Insérer un émoji</string>
<string name="no_emoji">Lapplication na pas encore collecté demojis personnalisés.</string>
<string name="logout_account_confirmation">Voulez-vous vraiment déconnecter le compte @%1$s@%2$s \?</string>
<!-- Status -->
<string name="no_status">Aucun pouet à afficher</string>
<string name="favourite_add">Ajouter ce message aux favoris \?</string>
<string name="favourite_remove">Supprimer ce message des favoris \?</string>
<string name="reblog_add">Partager ce message \?</string>
<string name="reblog_remove">Annuler le partage de ce message \?</string>
<string name="no_status">Aucun message à afficher</string>
<string name="favourite_add">Ajouter ce message aux favoris \?</string>
<string name="favourite_remove">Supprimer ce message des favoris \?</string>
<string name="reblog_add">Partager ce message \?</string>
<string name="reblog_remove">Annuler le partage de ce message \?</string>
<string name="more_action_1">Ignorer</string>
<string name="more_action_2">Bloquer</string>
<string name="more_action_3">Signaler</string>
@ -122,8 +122,8 @@
<!-- TOOT -->
<string name="toot_select_image_error">Une erreur sest produite lors de la sélection du média!</string>
<string name="toot_delete_media">Supprimer le média ?</string>
<string name="toot_error_no_content">Votre message est vide !</string>
<string name="toot_sent">Le message a été envoyé !</string>
<string name="toot_error_no_content">Votre message est vide !</string>
<string name="toot_sent">Le message a été envoyé !</string>
<string name="toot_sensitive">Contenu sensible ?</string>
<string name="no_draft">Aucun brouillon !</string>
<string name="choose_accounts">Choisissez un compte</string>
@ -149,9 +149,9 @@
<string name="followers_cnt">Abonné·e·s \n %1$s</string>
<string name="reject">Rejeter</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Aucun message programmé à afficher !</string>
<string name="remove_scheduled">Supprimer le message programmé \?</string>
<string name="toot_scheduled">Le message a été programmé !</string>
<string name="no_scheduled_toots">Aucun message programmé à afficher !</string>
<string name="remove_scheduled">Supprimer le message programmé \?</string>
<string name="toot_scheduled">Le message a été programmé !</string>
<string name="toot_scheduled_date">La date doit être supérieure à lheure actuelle!</string>
<!-- timed mute -->
<string name="timed_mute_date_error">Le délai pour rendre muet doit être supérieur à une minute.</string>
@ -177,10 +177,10 @@
<string name="toast_unmute">Le compte n\'est plus masqué !</string>
<string name="toast_follow">Le compte est suivi !</string>
<string name="toast_unfollow">Le compte n\'est plus suivi !</string>
<string name="toast_reblog">Le message a été partagé !</string>
<string name="toast_unreblog">Le pouet a été supprimé des partages !</string>
<string name="toast_favourite">Le pouet a été ajouté aux favoris !</string>
<string name="toast_unfavourite">Le pouet a été supprimé des favoris !</string>
<string name="toast_reblog">Le message a été partagé !</string>
<string name="toast_unreblog">Le message a été supprimé des partages !</string>
<string name="toast_favourite">Le message a été ajouté aux favoris !</string>
<string name="toast_unfavourite">Le message a été supprimé des favoris !</string>
<string name="toast_error">Oups ! Une erreur sest produite!</string>
<string name="toast_code_error">Une erreur sest produite! Linstance na retourné aucun code d\autorisation!</string>
<string name="toast_error_instance">Le nom de linstance ne semble pas être valide!</string>
@ -287,7 +287,7 @@
<string name="context_public">Fils publics</string>
<string name="context_notification">Notifications</string>
<string name="context_conversation">Discussions</string>
<string name="filter_keyword_explanations">Sera trouvé sans que la casse ou lavertissement de contenu du pouet soit pris en compte</string>
<string name="filter_keyword_explanations">Sera trouvé sans que la casse ou lavertissement de contenu du message soit pris en compte</string>
<string name="context_drop">Supprimer plutôt que de cacher</string>
<string name="context_drop_explanations">Les messages filtrés disparaîtront irrémédiablement même si le filtre est supprimé ultérieurement</string>
<string name="context_whole_word_explanations">Lorsque le mot-clef ou la phrase-clef est uniquement alphanumérique, ça sera uniquement appliqué sil correspond au mot entier</string>
@ -439,19 +439,19 @@
<string name="sign_up">Sinscrire</string>
<string name="validation_needed">Cette instance fonctionne avec des invitations. Votre compte devra être approuvé manuellement par un·e administrateur·rice pour qu\'il devienne utilisable.</string>
<string name="password_error">Les mots de passe ne sont pas identiques !</string>
<string name="email_error">L\'e-mail ne semble pas être valide !</string>
<string name="email_indicator">Vous recevrez un e-mail de confirmation</string>
<string name="email_error">L\'adresse ne semble pas être valide !</string>
<string name="email_indicator">Vous recevrez un courriel de confirmation</string>
<string name="password_indicator">Utilisez au moins 8 caractères</string>
<string name="password_too_short">Le mot de passe doit contenir au moins 8 caractères</string>
<string name="username_error">Le nom d\'utilisateur·rice doit contenir uniquement des lettres, des chiffres et des caractères de soulignement</string>
<string name="account_created">Compte créé !</string>
<string name="account_created_message"> Votre compte a été créé !
<string name="account_created_message"> Votre compte a été créé !
\n
\n Pensez à valider votre courriel dans les 48 prochaines heures.
\n
\n Vous pouvez maintenant connecter votre compte en écrivant <b>%1$s</b> dans le premier champ et appuyer sur <b>Se connecter</b>.
\n
\n <b>Important</b> : Si votre instance nécessite une validation, vous recevrez un courriel une fois quelle sera validée ! </string>
\n <b>Important</b> : Si votre instance nécessite une validation, vous recevrez un courriel une fois quelle sera validée ! </string>
<string name="save_draft">Sauvegarder le message dans les brouillons \?</string>
<string name="administration">Administration</string>
<string name="reports">Rapports</string>
@ -481,7 +481,9 @@
<string name="set_enable_crash_report">Activer les rapports de plantage</string>
<string name="set_enable_crash_report_indication">Si activé, un rapport de plantage sera créé localement et vous pourrez le partager.</string>
<string name="crash_title">Fedilab sest arrêté :(</string>
<string name="crash_message">Vous pouvez menvoyer le rapport derreur par mail. Cela aidera à corriger le problème :)\n\nVous pouvez y ajouter des informations supplémentaires. Merci!</string>
<string name="crash_message">Vous pouvez menvoyer le rapport derreur par courriel. Cela aidera à corriger le problème :)
\n
\nVous pouvez y ajouter des informations supplémentaires. Merci!</string>
<string name="visibility">Visibilité</string>
<string name="set_disable_animated_emoji">Désactiver les émojis animés personnalisés</string>
<string name="report_account">Signaler le compte</string>
@ -582,14 +584,14 @@
<string name="replace_twitter">Twitter</string>
<string name="report_1_mute_title">Masquer %1$s</string>
<string name="report_1_block_title">Bloquer %1$s</string>
<string name="report_3_title">Quelles sont les règles qui sont violées \?</string>
<string name="report_3_title">Quelles sont les règles qui sont violées \?</string>
<string name="report_all_more">Sélectionnez tous les éléments qui sappliquent</string>
<string name="report_more">Y a-t-il autre chose que vous pensez que nous devrions savoir \?</string>
<string name="report_more">Y a-t-il autre chose que vous pensez que nous devrions savoir \?</string>
<string name="report_more_additional">Commentaires supplémentaires</string>
<string name="report_more_remote">Le compte provient dun autre serveur. Envoyer une copie anonymisée du rapport là-bas aussi \?</string>
<string name="report_more_remote">Le compte provient dun autre serveur. Envoyer une copie anonymisée du signalement là-bas aussi \?</string>
<string name="report_more_forward">Transférer à %1$s</string>
<string name="report_sent">Le signalement a été effectué !</string>
<string name="dont_have_an_account">Vous navez pas de compte \?</string>
<string name="dont_have_an_account">Vous navez pas de compte \?</string>
<string name="join_the_fediverse">Rejoignez le Fédiverse</string>
<string name="invite_join_the_fediverse">Salut ! Nous vous invitons à rejoindre le Fédiverse.</string>
<string name="notif_display_mentions">Mentions</string>
@ -628,7 +630,7 @@
<string name="set_notifications_page">Nombre de notifications par chargement</string>
<string name="not_valid_list_name">Le nom de la liste nest pas valide !</string>
<string name="no_account_in_list">Aucun compte trouvé pour cette liste !</string>
<string name="toast_unbookmark">Le pouet a été retiré de vos marque-pages !</string>
<string name="toast_unbookmark">Le message a été retiré de vos marque-pages !</string>
<string name="replace_youtube_description">Utiliser une interface alternative pour YouTube</string>
<string name="replace_twitter_description">Utiliser une interface alternative pour Twitter</string>
<string name="replace_instagram">Instagram</string>
@ -662,21 +664,21 @@
<string name="report_title">Signalement de %1$s</string>
<string name="report_indication_title_status">Dite-nous ce qui ne va pas avec cette publication</string>
<string name="report_indication_title_status_more">Choisissez l\'option la plus adaptée</string>
<string name="report_val1">Je n\'aime pas cela</string>
<string name="report_val_more1">Ce n\'est pas quelque-chose que vous voulez voir</string>
<string name="report_val1">Je n\'aime pas ça</string>
<string name="report_val_more1">Ce n\'est pas quelque chose que vous voulez voir</string>
<string name="report_val2">C\'est du spam</string>
<string name="report_val_more2">Liens malveillants, faux engagement, ou réponses répétitives</string>
<string name="report_val_more3">Vous savez que cela enfreint des règles spécifiques</string>
<string name="report_val4">C\'est quelque-chose d\'autre</string>
<string name="report_val_more2">Liens malveillants, faux engagement ou réponses répétitives</string>
<string name="report_val_more3">Vous savez qu\'elle enfreint des règles spécifiques</string>
<string name="report_val4">C\'est quelque chose d\'autre</string>
<string name="report_val_more4">Ce problème ne rentre pas dans d\'autres catégories</string>
<string name="report_1_title">Vous ne voulez pas voir ceci \?</string>
<string name="report_1_title_more">Voici vos options pour contrôler ce que vous voyez sur Mastodon :</string>
<string name="report_1_title">Vous ne voulez pas voir ceci \?</string>
<string name="report_1_title_more">Voici vos options pour contrôler ce que vous voyez sur Mastodon :</string>
<string name="report_1_unfollow_title">Se désabonner de %1$s</string>
<string name="report_1_unfollow">Vous suivez ce compte. Pour ne plus voir ses messages dans votre fil principal, désabonnez-vous-en.</string>
<string name="report_1_mute">Vous ne verrez pas ses messages. Iel peut toujours vous suivre et voir vos messages mais ne saura pas quiel a été mis·e en sourdine.</string>
<string name="report_1_block">Vous ne verrez pas ses messages. Iel ne pourra pas voir vos messages ou vous suivre. Cette personne ne pourra pas voir quelle a été bloquée.</string>
<string name="report_2_title">Y a-t-il des messages qui étayent ce rapport \?</string>
<string name="about_mastodon">\"Mastodon n\'est pas un seul site comme Twitter ou Facebook, c\'est un réseau de milliers de communautés opérées par différentes organisations et individus qui fournissent une expérience de réseau social fluide.\"</string>
<string name="report_2_title">Y a-t-il des messages qui étayent ce signalement \?</string>
<string name="about_mastodon">« Mastodon n\'est pas un seul site comme Twitter ou Facebook, c\'est un réseau de milliers de communautés opérées par différentes organisations et individus qui fournissent une expérience de réseau social fluide. »</string>
<string name="notif_display_follows">Abonnements</string>
<string name="notif_display_updates_from_people">Nouvelles des gens</string>
<string name="delete_notification_all_warning">Êtes-vous sûr·e de vouloir supprimer toutes les notification \? Cette action est irréversible.</string>
@ -685,9 +687,9 @@
<string name="notification_sounds">Sons des notifications</string>
<string name="pref_theme_base_summary">Choisissez si la base du thème devrait être sombre ou clair</string>
<string name="pref_contributor_summary">Choisir un thème fait par les contributeurs</string>
<string name="toast_bookmark">Le pouet a été ajouté à vos marque-pages !</string>
<string name="toast_bookmark">Le message a été ajouté à vos marque-pages !</string>
<string name="set_display_bookmark_indication">Toujours afficher le bouton marque-page</string>
<string name="report_val3">Cela viole les règles du serveur</string>
<string name="report_val3">Elle viole les règles du serveur</string>
<string name="display">Afficher</string>
<string name="hide_content">Masquer le contenu &lt;</string>
<string name="also_favourite_by">"Également préféré par : "</string>
@ -719,4 +721,7 @@
<string name="label_eraser_mode">Mode gomme</string>
<string name="label_rectangle">Rectangle</string>
<string name="msg_save_image">Souhaitez-vous quitter sans enregistrer limage \?</string>
<string name="messages_in_cache_for_home">Messages dans le cache pour lAccueil</string>
<string name="messages_in_cache_for_other_timelines">Messages dans le cache pour les fils</string>
<string name="label_oval">Ovale</string>
</resources>

View File

@ -15,17 +15,17 @@
<string name="save_over">Media salvato</string>
<string name="download_from" formatted="false">File: %1$s</string>
<string name="password">Password</string>
<string name="email">Email</string>
<string name="email">E-mail</string>
<string name="accounts">Account</string>
<string name="toots">Toot</string>
<string name="toots">Messaggi</string>
<string name="tags">Etichette</string>
<string name="save">Salva</string>
<string name="instance">Istanza</string>
<string name="instance_example">Istanza: mastodon.social</string>
<string name="toast_account_changed" formatted="false">Ora funziona con l\'account %1$s</string>
<string name="add_account">Aggiungi un account</string>
<string name="clipboard">Il contenuto del toot è stato copiato negli appunti</string>
<string name="clipboard_url">L\'URL del toot è stato copiato negli appunti</string>
<string name="clipboard">Il contenuto del messaggio è stato copiato negli appunti</string>
<string name="clipboard_url">L\'URL del messaggio è stato copiato negli appunti</string>
<string name="camera">Fotocamera</string>
<string name="delete_all">Elimina tutto</string>
<string name="schedule">Pianifica</string>
@ -56,18 +56,18 @@
<string name="notifications">Notifiche</string>
<string name="follow_request">Richieste di follow</string>
<string name="settings">Impostazioni</string>
<string name="send_email">Invia un\'email</string>
<string name="scheduled_toots">Toot programmati</string>
<string name="send_email">Invia un\'e-mail</string>
<string name="scheduled_toots">Messaggi programmati</string>
<string name="disclaimer_full">Le informazioni qui sotto potrebbero riflettere in modo incompleto il profilo dell\'utente.</string>
<string name="insert_emoji">Inserisci emoji</string>
<string name="no_emoji">L\'app non raccoglie emoji personalizzate per il momento.</string>
<string name="logout_account_confirmation">Sei sicuro di volerti disconnettere @%1$s@%2$s?</string>
<!-- Status -->
<string name="no_status">Nessun toot da mostrare</string>
<string name="favourite_add">Aggiungere questo toot ai tuoi preferiti?</string>
<string name="favourite_remove">Rimuovere questo toot dai tuoi preferiti?</string>
<string name="reblog_add">Condividere questo toot?</string>
<string name="reblog_remove">Smettere di condividere questo toot?</string>
<string name="no_status">Nessun messaggio da mostrare</string>
<string name="favourite_add">Aggiungere questo messaggio ai tuoi preferiti\?</string>
<string name="favourite_remove">Rimuovere questo messaggio dai tuoi preferiti\?</string>
<string name="reblog_add">Condividere questo messaggio\?</string>
<string name="reblog_remove">Smettere di condividere questo messaggo\?</string>
<string name="more_action_1">Silenzia</string>
<string name="more_action_2">Blocca</string>
<string name="more_action_3">Segnala</string>
@ -122,8 +122,8 @@
<!-- TOOT -->
<string name="toot_select_image_error">Si è verificato un errore durante la selezione del media!</string>
<string name="toot_delete_media">Eliminare questo media?</string>
<string name="toot_error_no_content">Il tuo toot è vuoto!</string>
<string name="toot_sent">Il toot è stato inviato!</string>
<string name="toot_error_no_content">Il tuo messaggio è vuoto!</string>
<string name="toot_sent">Il messaggio è stato inviato!</string>
<string name="toot_sensitive">Contenuto sensibile?</string>
<string name="no_draft">Nessuna bozza!</string>
<string name="choose_accounts">Seleziona un account</string>
@ -143,14 +143,15 @@
<!-- Accounts -->
<string name="no_accounts">Nessun account da visualizzare</string>
<string name="no_follow_request">Nessuna richiesta di follow</string>
<string name="status_cnt">Toot \n %1$s</string>
<string name="status_cnt">Messaggi
\n %1$s</string>
<string name="following_cnt">Seguendo \n %1$s</string>
<string name="followers_cnt">Follower \n %1$s</string>
<string name="reject">Rifiuta</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Nessun toot programmato da visualizzare!</string>
<string name="remove_scheduled">Eliminare toot programmato?</string>
<string name="toot_scheduled">Il toot è stato programmato!</string>
<string name="no_scheduled_toots">Nessun messaggio programmato da visualizzare!</string>
<string name="remove_scheduled">Eliminare messaggio programmato\?</string>
<string name="toot_scheduled">Il messaggio è stato programmato!</string>
<string name="toot_scheduled_date">La data di programmazione deve essere successiva all\'ora corrente!</string>
<!-- timed mute -->
<string name="timed_mute_date_error">Il tempo di silenziamento deve essere maggiore di un minuto.</string>
@ -176,10 +177,10 @@
<string name="toast_unmute">Questo account non è più silenziato!</string>
<string name="toast_follow">L\'account è seguito!</string>
<string name="toast_unfollow">L\'account non è più seguito!</string>
<string name="toast_reblog">Il toot è stato ricondiviso!</string>
<string name="toast_unreblog">Il toot non è più ricondiviso!</string>
<string name="toast_favourite">Il toot è stato aggiunto ai tuoi preferiti!</string>
<string name="toast_unfavourite">Il toot è stato rimosso dai tuoi preferiti!</string>
<string name="toast_reblog">Il messaggio è stato ricondiviso!</string>
<string name="toast_unreblog">Il messaggio non è più ricondiviso!</string>
<string name="toast_favourite">Il messaggio è stato aggiunto ai tuoi preferiti!</string>
<string name="toast_unfavourite">Il messaggio è stato rimosso dai tuoi preferiti!</string>
<string name="toast_error">Oops! Si è verificato un errore!</string>
<string name="toast_code_error">Si è verificato un errore! L\'istanza non ha restituito un codice di autorizzazione!</string>
<string name="toast_error_instance">Il dominio dell\'istanza non sembra essere valido!</string>
@ -188,7 +189,7 @@
<string name="nothing_to_do">Nessuna azione può essere intrapresa</string>
<string name="toast_error_translate">Si è verificato un errore durante la traduzione!</string>
<!-- Settings -->
<string name="set_toots_page">Numero di toot per caricamento</string>
<string name="set_toots_page">Numero di messaggi per caricamento</string>
<string name="set_disable_gif">Disabilita avatar GIF</string>
<string name="set_notif_follow">Notifica quando qualcuno ti segue</string>
<string name="set_notif_follow_share">Notifica quando qualcuno ricondivide al tuo stato</string>
@ -267,7 +268,7 @@
<string name="poxy_port">Porta</string>
<string name="poxy_login">Login</string>
<string name="poxy_password">Password</string>
<string name="set_share_details">Aggiungi dettagli del toot quando condividi</string>
<string name="set_share_details">Aggiungi dettagli del messaggio quando condividi</string>
<string name="support_the_app_on_liberapay">Supporta l\'app su Liberapay</string>
<string name="alert_regex">C\'è un errore nell\'espressione regolare!</string>
<string name="toast_instance_unavailable">Nessuna timeline trovata su questa istanza!</string>
@ -286,9 +287,9 @@
<string name="context_public">Timeline pubblica</string>
<string name="context_notification">Notifiche</string>
<string name="context_conversation">Conversazioni</string>
<string name="filter_keyword_explanations">Il confronto sarà eseguito ignorando minuscole/maiuscole e i content warning di un toot</string>
<string name="filter_keyword_explanations">Il confronto sarà eseguito ignorando minuscole/maiuscole e i content warning di un messaggio</string>
<string name="context_drop">Ignorare invece di nascondere</string>
<string name="context_drop_explanations">I toot filtrati scompariranno in modo irreversibile, anche se il filtro verrà successivamente rimosso</string>
<string name="context_drop_explanations">I messaggii filtrati scompariranno in modo irreversibile, anche se il filtro verrà successivamente rimosso</string>
<string name="context_whole_word_explanations">Quando la parola chiave o la frase è solo alfanumerica, sarà applicato solo se corrisponde alll\'intera parola</string>
<string name="context_whole_word">Parola intera</string>
<string name="filter_context">Filtra contesti</string>
@ -303,7 +304,7 @@
<string name="channel_notif_fav">Un nuovo mi piace</string>
<string name="channel_notif_mention">Una nuova menzione</string>
<string name="channel_notif_poll">Sondaggio terminato</string>
<string name="channel_notif_backup">Backup dei toot</string>
<string name="channel_notif_backup">Backup dei messaggi</string>
<string name="channel_notif_status">Nuovi post</string>
<string name="channel_notif_media">Scarica i media</string>
<string name="select_sound">Seleziona tono</string>
@ -315,11 +316,11 @@
<string name="peertube_instance">Istanza Peertube</string>
<string name="set_display_emoji">Utilizza le Emoji One</string>
<string name="information">Informazioni</string>
<string name="set_display_card">Mostra anteprime in tutti i toot</string>
<string name="set_display_card">Mostra anteprime in tutti i messaggi</string>
<string name="account_id_clipbloard">L\'ID dell\'account è stato copiato negli appunti!</string>
<string name="set_change_locale">Cambia lingua</string>
<string name="truncate_long_toots">Tronca toot lunghi</string>
<string name="set_truncate_toot">Tronca toot più lunghi di \'x\' linee. Zero significa disabilitato.</string>
<string name="truncate_long_toots">Tronca messaggi lunghi</string>
<string name="set_truncate_toot">Tronca messaggi più lunghi di x linee. Zero significa disabilitato.</string>
<string name="display_toot_truncate">Mostra di più</string>
<string name="hide_toot_truncate">Mostra meno</string>
<string name="tags_already_stored">L\'etichetta esiste già!</string>
@ -365,8 +366,8 @@
<string name="category">Categoria</string>
<string name="description">Descrizione</string>
<string name="share">Condividi</string>
<string name="toots_server">Toot (Server)</string>
<string name="toots_client">Toot (Dispositivo)</string>
<string name="toots_server">Messaggi (Server)</string>
<string name="toots_client">Messaggi (Dispositivo)</string>
<string name="settings_category_label_timelines">Timeline</string>
<string name="settings_category_label_interface">Interfaccia</string>
<string name="contact">Contatti</string>
@ -436,17 +437,19 @@
<string name="sign_up">Registrati</string>
<string name="validation_needed">Questa istanza funziona con gli inviti. Il tuo account deve essere approvato manualmente da un amministratore prima di essere utilizzabile.</string>
<string name="password_error">Le password non corrispondono!</string>
<string name="email_error">L\'indirizzo email non sembra essere valido!</string>
<string name="email_indicator">Ti sarà inviata una conferma via email</string>
<string name="email_error">L\'indirizzo e-mail non sembra essere valido!</string>
<string name="email_indicator">Ti sarà inviata una conferma via e-mail</string>
<string name="password_indicator">Usa almeno 8 caratteri</string>
<string name="password_too_short">La password deve contenere almeno 8 caratteri</string>
<string name="username_error">Il nome utente può contenere solo lettere, numeri e trattini bassi</string>
<string name="account_created">Account creato!</string>
<string name="account_created_message"> Il tuo account è stato creato!\n\n
Dovresti confermare la tua email entro le prossime 48 ore.\n\n
Ora puoi connetterti al tuo account scrivendo <b>%1$s</b> nel primo campo e premendo <b>Connetti</b>.\n\n
<b>Importante</b>: Se la tua istanza richiede l\'approvazione, riceverai una mail solo quando sarà approvato il tuo account!
</string>
<string name="account_created_message"> Il tuo account è stato creato!
\n
\n Dovresti confermare la tua e-mail entro le prossime 48 ore.
\n
\n Ora puoi connetterti al tuo account scrivendo <b>%1$s</b> nel primo campo e premendo <b>Connetti</b>.
\n
\n <b>Importante</b>: Se la tua istanza richiede l\'approvazione, riceverai un\'e-mail solo quando sarà approvato il tuo account! </string>
<string name="save_draft">Salvare il messaggio in bozze?</string>
<string name="administration">Amministrazione</string>
<string name="reports">Segnalazioni</string>
@ -476,7 +479,9 @@
<string name="set_enable_crash_report">Abilita segnalazioni di crash</string>
<string name="set_enable_crash_report_indication">Se abilitato, una segnalazione di crash sarà creata localmente e quindi sarai in grado di condividerla.</string>
<string name="crash_title">Fedilab ha smesso di funzionare :-(</string>
<string name="crash_message">Puoi inviarmi via email il rapporto sul problema. Mi aiuterà a risolverlo :-)\n\nPuoi aggiungere ulteriori commenti. Grazie!</string>
<string name="crash_message">Puoi inviarmi via e-mail il rapporto sul problema. Mi aiuterà a risolverlo :-)
\n
\nPuoi aggiungere ulteriori commenti. Grazie!</string>
<string name="visibility">Visibilità</string>
<string name="set_disable_animated_emoji">Disabilita emoji animate personalizzate</string>
<string name="report_account">Segnala account</string>
@ -570,4 +575,15 @@
<string name="no_distributors_found">Nessun distributore trovato!</string>
<string name="no_distributors_explanation">Hai bisogno di un distributore per ricevere notifiche push.\nTroverai maggiori dettagli a %1$s.\n\nPuoi anche disabilitare le notifiche push nelle impostazioni per ignorare quel messaggio.</string>
<string name="select_distributors">Seleziona un distributore</string>
<string name="messages_stored_in_drafts">Messaggi memorizzati nelle bozze</string>
<string name="messages_in_cache_for_home">Messaggi in cache per Inizio</string>
<string name="replace_youtube">YouTube</string>
<string name="messages_in_cache_for_other_timelines">Messaggi nella cache per altre cronologie</string>
<string name="fetch_more_messages">Recupera altri messaggi…</string>
<string name="replace_youtube_description">Usa un frontend alternativo per YouTube</string>
<string name="report_val1">Non mi piace</string>
<string name="toots_visibility_title">Visibilità predefinita dei messaggi:</string>
<string name="toast_bookmark">Il messaggio è stato aggiunto ai tuoi preferiti!</string>
<string name="toast_unbookmark">Il messaggio è stato rimosso dai tuoi preferiti!</string>
<string name="set_accounts_page">Numero di account per caricamento</string>
</resources>

View File

@ -437,16 +437,18 @@
<string name="validation_needed">Dit geval werkt met uitnodigingen. Uw account moet handmatig worden goedgekeurd door een beheerder voordat het bruikbaar is.</string>
<string name="password_error">Wachtwoorden komen niet overeen!</string>
<string name="email_error">De e-mail lijkt niet geldig te zijn!</string>
<string name="email_indicator">U ontvangt een bevestigingsmail</string>
<string name="email_indicator">U ontvangt een bevestigingse-mail</string>
<string name="password_indicator">Gebruik minstens 8 karakters</string>
<string name="password_too_short">Password should contain at least 8 characters</string>
<string name="username_error">Username should only contain letters, numbers and underscores</string>
<string name="account_created">Account aangemaakt!</string>
<string name="account_created_message"> Your account has been created!\n\n
Think to validate your email within the 48 next hours.\n\n
You can now connect your account by writing <b>%1$s</b> in the first field and click on <b>Connect</b>.\n\n
<b>Important</b>: If your instance required validation, you will receive an email once it is validated!
</string>
<string name="account_created_message"> Uw account is aangemaakt!
\n
\n Denk eraan om uw e-mail te valideren binnen de 48 komende uren.
\n
\n U kunt nu uw account verbinden door <b>%1$s</b> in het eerste veld te schrijven en op <b>Verbinden</b> te tikken.
\n
\n <b>Belangrijk</b>: Als uw instantie moet worden gevalideerd, ontvangt u een e-mail zodra deze is gevalideerd! </string>
<string name="save_draft">Het bericht opslaan in concepten?</string>
<string name="administration">Administratie</string>
<string name="reports">Rapporten</string>

View File

@ -15,7 +15,7 @@
<string name="save_over">Pliki zapisane</string>
<string name="download_from" formatted="false">Plik: %1$s</string>
<string name="password">Hasło</string>
<string name="email">Email</string>
<string name="email">E-mail</string>
<string name="accounts">Konta</string>
<string name="toots">Wpisy</string>
<string name="tags">Tagi</string>
@ -56,7 +56,7 @@
<string name="notifications">Powiadomienia</string>
<string name="follow_request">Prośby o śledzenie</string>
<string name="settings">Ustawienia</string>
<string name="send_email">Wyślij email</string>
<string name="send_email">Wyślij e-mail</string>
<string name="scheduled_toots">Zaplanowane wpisy</string>
<string name="disclaimer_full">Poniższe informacje mogą nie odzwierciedlać w pełni profilu użytkownika.</string>
<string name="insert_emoji">Wstaw emoji</string>
@ -488,7 +488,9 @@
<string name="set_enable_crash_report">Włącz raportowanie błędów</string>
<string name="set_enable_crash_report_indication">Jeśli włączone, raport o awarii zostanie utworzony lokalnie, a następnie będziesz mógł go udostępnić.</string>
<string name="crash_title">Fedilab został zatrzymany :(</string>
<string name="crash_message">Możesz wysłać mi email z raportem o błędzie. To pomoże go naprawić :)\n\nMożesz dopisać coś więcej. Dziękuję!</string>
<string name="crash_message">Możesz wysłać mi e-mail z raportem o błędzie. To pomoże go naprawić :)
\n
\nMożesz dopisać coś więcej. Dziękuję!</string>
<string name="visibility">Widoczność</string>
<string name="set_disable_animated_emoji">Wyłącz niestandardowe animowane emotikony</string>
<string name="report_account">Zgłoś konto</string>
@ -650,7 +652,7 @@
<string name="set_accounts_page">Liczba rachunków na ładunek</string>
<string name="set_notifications_page">Liczba powiadomień na ładunek</string>
<string name="report_1_block">Nie będziesz widzieć ich postów. Osoby te nie będą mogły widzieć Twoich postów ani Cię śledzić. Będą mogli powiedzieć, że zostali zablokowani.</string>
<string name="about_mastodon">\"Mastodon nie jest pojedynczą stroną internetową, jak Twitter czy Facebook, to sieć tysięcy społeczności obsługiwanych przez różne organizacje i osoby, które zapewniają płynne działanie mediów społecznościowych.\"</string>
<string name="about_mastodon">Mastodon nie jest pojedynczą stroną internetową, jak Twitter czy Facebook, to sieć tysięcy społeczności obsługiwanych przez różne organizacje i osoby, które zapewniają płynne działanie mediów społecznościowych.</string>
<string name="customize_timelines">Dostosowywanie osi czasu</string>
<string name="resolved">Rozwiązany</string>
<string name="status">Stan</string>

View File

@ -744,4 +744,11 @@
<string name="label_rectangle">Chữ nhật</string>
<string name="label_line">Đường thẳng</string>
<string name="label_eraser_mode">Chế độ Xóa</string>
<string name="delete_cache">Xóa cache</string>
<string name="messages_in_cache_for_home">Tút trong cache cho Bảng Tin</string>
<string name="messages_stored_in_drafts">Tút lưu trong nháp</string>
<string name="delete_cache_message">Bạn có chắc muốn xóa cache\? Nếu bạn có tút nháp đính kèm media, nó sẽ bị mất.</string>
<string name="messages_in_cache_for_other_timelines">Tút trong cache những Bảng Tin khác</string>
<string name="files_cache_size">Kích cỡ cache</string>
<string name="clear_cache">Xóa cache</string>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB