diff --git a/app/build.gradle b/app/build.gradle index ef78a3c7..d22d76ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { minSdk 21 targetSdk 32 - versionCode 443 - versionName "3.10.0" + versionCode 446 + versionName "3.11.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } flavorDimensions "default" diff --git a/app/src/main/assets/release_notes/notes.json b/app/src/main/assets/release_notes/notes.json index 0a3ee24c..f3818d4c 100644 --- a/app/src/main/assets/release_notes/notes.json +++ b/app/src/main/assets/release_notes/notes.json @@ -1,4 +1,19 @@ [ + { + "version": "3.11.0", + "code": "446", + "note": "Added:\n- Display all messages in threads from remote instances (when possible)\n- Allow to unmute/unfollow/unpin a tag from tag timelines\n- Display most used accounts in header menu for an easy switch\n- Automatically add the tag when composing from a tag timeline\n- Add a translate button at the bottom of messages (default: disabled)\n- Add account role in profiles\n- Translate morse\n\nChanged:\n- Disable animations after a refresh\n\nFixed:\n- Contact not working when composing\n- Status bar for black theme\n- Message duplicated in conversations when edited\n- Color issue on Android 5\n- Several crashes" + }, + { + "version": "3.10.2", + "code": "445", + "note": "Added:\n- Allow to unmute/unfollow/unpin a tag from tag timelines\n- Automatically add the tag when composing from a tag timeline\n- Add a translate button at the bottom of messages (default: disabled)\n- Add account role in profiles\n\nFixed:\n- Contact not working when composing\n- Status bar for black theme\n- Message duplicated in conversations when edited\n- Color issue on Android 5" + }, + { + "version": "3.10.1", + "code": "444", + "note": "Added:\n- Display all messages in threads from remote instances (when possible)\n* Only public messages for instances using the Mastodon API\n* A dedicated button is displayed at the top right when conditions are filled." + }, { "version": "3.10.0", "code": "443", diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 1f28b8be..7e43d4d0 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -697,7 +697,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class).getConnectedAccount(currentInstance, currentToken) .observe(BaseMainActivity.this, mastodonAccount -> { //Initialize static var - if (mastodonAccount != null) { + if (mastodonAccount != null && currentAccount != null) { currentAccount.mastodon_account = mastodonAccount; displayReleaseNotesIfNeeded(BaseMainActivity.this, false); new Thread(() -> { @@ -758,6 +758,57 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } }).start(); } + //Fetch recent used accounts + new Thread(() -> { + try { + List accounts = new Account(BaseMainActivity.this).getLastUsedAccounts(); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + if (accounts != null && accounts.size() > 0) { + Helper.loadPP(this, headerMainBinding.otherAccount1, accounts.get(0)); + headerMainBinding.otherAccount1.setVisibility(View.VISIBLE); + headerMainBinding.otherAccount1.setOnClickListener(v -> { + headerMenuOpen = false; + Toasty.info(BaseMainActivity.this, getString(R.string.toast_account_changed, "@" + accounts.get(0).mastodon_account.acct + "@" + accounts.get(0).instance), Toasty.LENGTH_LONG).show(); + BaseMainActivity.currentToken = accounts.get(0).token; + BaseMainActivity.currentUserID = accounts.get(0).user_id; + api = accounts.get(0).api; + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(PREF_USER_TOKEN, accounts.get(0).token); + editor.commit(); + //The user is now aut + //The user is now authenticated, it will be redirected to MainActivity + Intent mainActivity = new Intent(this, MainActivity.class); + startActivity(mainActivity); + finish(); + }); + if (accounts.size() > 1) { + Helper.loadPP(this, headerMainBinding.otherAccount2, accounts.get(1)); + headerMainBinding.otherAccount2.setVisibility(View.VISIBLE); + headerMainBinding.otherAccount2.setOnClickListener(v -> { + headerMenuOpen = false; + Toasty.info(BaseMainActivity.this, getString(R.string.toast_account_changed, "@" + accounts.get(1).mastodon_account.acct + "@" + accounts.get(1).instance), Toasty.LENGTH_LONG).show(); + BaseMainActivity.currentToken = accounts.get(1).token; + BaseMainActivity.currentUserID = accounts.get(1).user_id; + api = accounts.get(1).api; + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(PREF_USER_TOKEN, accounts.get(1).token); + editor.commit(); + //The user is now aut + //The user is now authenticated, it will be redirected to MainActivity + Intent mainActivity = new Intent(this, MainActivity.class); + startActivity(mainActivity); + finish(); + }); + } + } + }; + mainHandler.post(myRunnable); + + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); } protected abstract void rateThisApp(); diff --git a/app/src/main/java/app/fedilab/android/activities/BaseActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseActivity.java index 2f2b870f..e7ba4af8 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseActivity.java @@ -19,8 +19,11 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -81,6 +84,9 @@ public class BaseActivity extends AppCompatActivity { break; case "BLACK": setTheme(R.style.BlackAppTheme); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); currentThemeId = R.style.BlackAppTheme; break; case "DRACULA": @@ -115,6 +121,9 @@ public class BaseActivity extends AppCompatActivity { case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); setTheme(R.style.BlackAppTheme); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); currentThemeId = R.style.BlackAppTheme; break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/BaseAlertDialogActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseAlertDialogActivity.java index 48b51a25..734121bf 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseAlertDialogActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseAlertDialogActivity.java @@ -19,8 +19,11 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -74,6 +77,9 @@ public class BaseAlertDialogActivity extends AppCompatActivity { setTheme(R.style.SolarizedAlertDialog); break; case "BLACK": + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAlertDialog); break; case "DRACULA": @@ -102,6 +108,9 @@ public class BaseAlertDialogActivity extends AppCompatActivity { break; case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAlertDialog); break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java index e8f0111e..2776d2ec 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java @@ -19,8 +19,11 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -73,6 +76,9 @@ public class BaseBarActivity extends AppCompatActivity { setTheme(R.style.SolarizedAppThemeBar); break; case "BLACK": + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAppThemeBar); break; case "DRACULA": @@ -102,6 +108,9 @@ public class BaseBarActivity extends AppCompatActivity { break; case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAppThemeBar); break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java index 77f2df73..0f266864 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java @@ -19,8 +19,11 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -73,6 +76,9 @@ public class BaseTransparentActivity extends AppCompatActivity { setTheme(R.style.TransparentSolarized); break; case "BLACK": + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.TransparentBlack); break; case "DRACULA": @@ -102,6 +108,9 @@ public class BaseTransparentActivity extends AppCompatActivity { break; case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.BLACK); setTheme(R.style.TransparentBlack); break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java index c9cedf5b..1688d292 100644 --- a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java @@ -402,8 +402,8 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana return true; } - private void onRetrieveContact(PopupContactBinding binding, List accounts) { - binding.loader.setVisibility(View.GONE); + private void onRetrieveContact(PopupContactBinding popupContactBinding, List accounts) { + popupContactBinding.loader.setVisibility(View.GONE); if (accounts == null) { accounts = new ArrayList<>(); } @@ -413,8 +413,9 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana checkedValues.add(composeAdapter.getLastComposeContent().contains("@" + account.acct)); } AccountsReplyAdapter contactAdapter = new AccountsReplyAdapter(contacts, checkedValues); - binding.lvAccountsSearch.setAdapter(contactAdapter); - binding.lvAccountsSearch.setLayoutManager(new LinearLayoutManager(ComposeActivity.this)); + contactAdapter.actionDone = ComposeActivity.this; + popupContactBinding.lvAccountsSearch.setAdapter(contactAdapter); + popupContactBinding.lvAccountsSearch.setLayoutManager(new LinearLayoutManager(ComposeActivity.this)); } @Override @@ -869,10 +870,14 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana private boolean canBeSent(StatusDraft statusDraft) { - if (statusDraft == null || statusDraft.statusDraftList == null || statusDraft.statusDraftList.isEmpty()) { + if (statusDraft == null) { return false; } - Status statusCheck = statusDraft.statusDraftList.get(0); + List statuses = statusDraft.statusDraftList; + if (statuses == null || statuses.size() == 0) { + return false; + } + Status statusCheck = statuses.get(0); if (statusCheck == null) { return false; } diff --git a/app/src/main/java/app/fedilab/android/activities/ContextActivity.java b/app/src/main/java/app/fedilab/android/activities/ContextActivity.java index 243e90b0..5a87a62d 100644 --- a/app/src/main/java/app/fedilab/android/activities/ContextActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ContextActivity.java @@ -18,6 +18,7 @@ package app.fedilab.android.activities; import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.ui.drawer.StatusAdapter.sendAction; +import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; @@ -33,6 +34,9 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Status; @@ -43,19 +47,22 @@ import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonContext; import app.fedilab.android.viewmodel.mastodon.StatusesVM; +import es.dmoral.toasty.Toasty; -public class ContextActivity extends BaseActivity { +public class ContextActivity extends BaseActivity implements FragmentMastodonContext.FirstMessage { public static boolean expand; public static boolean displayCW; public static Resources.Theme theme; Fragment currentFragment; + private Status firstMessage; + private String remote_instance; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - app.fedilab.android.databinding.ActivityConversationBinding binding = ActivityConversationBinding.inflate(getLayoutInflater()); + ActivityConversationBinding binding = ActivityConversationBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); ActionBar actionBar = getSupportActionBar(); @@ -75,8 +82,10 @@ public class ContextActivity extends BaseActivity { Bundle b = getIntent().getExtras(); displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false); Status focusedStatus = null; // or other values - if (b != null) + if (b != null) { focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS); + remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null); + } if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) { finish(); return; @@ -84,29 +93,34 @@ public class ContextActivity extends BaseActivity { MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account); Bundle bundle = new Bundle(); bundle.putSerializable(Helper.ARG_STATUS, focusedStatus); - currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, new FragmentMastodonContext(), bundle, null, null); - StatusesVM timelinesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class); - timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(ContextActivity.this, status -> { - if (status != null) { - StatusCache statusCache = new StatusCache(); - statusCache.instance = BaseMainActivity.currentInstance; - statusCache.user_id = BaseMainActivity.currentUserID; - statusCache.status = status; - statusCache.status_id = status.id; - //Update cache - new Thread(() -> { - try { - new StatusCache(getApplication()).updateIfExists(statusCache); - Handler mainHandler = new Handler(Looper.getMainLooper()); - //Update UI - Runnable myRunnable = () -> sendAction(ContextActivity.this, Helper.ARG_STATUS_ACTION, status, null); - mainHandler.post(myRunnable); - } catch (DBException e) { - e.printStackTrace(); - } - }).start(); - } - }); + bundle.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance); + FragmentMastodonContext fragmentMastodonContext = new FragmentMastodonContext(); + fragmentMastodonContext.firstMessage = this; + currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, fragmentMastodonContext, bundle, null, null); + if (remote_instance == null) { + StatusesVM timelinesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class); + timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(ContextActivity.this, status -> { + if (status != null) { + StatusCache statusCache = new StatusCache(); + statusCache.instance = BaseMainActivity.currentInstance; + statusCache.user_id = BaseMainActivity.currentUserID; + statusCache.status = status; + statusCache.status_id = status.id; + //Update cache + new Thread(() -> { + try { + new StatusCache(getApplication()).updateIfExists(statusCache); + Handler mainHandler = new Handler(Looper.getMainLooper()); + //Update UI + Runnable myRunnable = () -> sendAction(ContextActivity.this, Helper.ARG_STATUS_ACTION, status, null); + mainHandler.post(myRunnable); + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); + } + }); + } } @@ -126,6 +140,18 @@ public class ContextActivity extends BaseActivity { } else { itemDisplayCW.setIcon(R.drawable.ic_outline_remove_red_eye_24); } + MenuItem action_remote = menu.findItem(R.id.action_remote); + if (remote_instance != null) { + action_remote.setVisible(false); + } else { + if (firstMessage != null && !firstMessage.visibility.equalsIgnoreCase("direct") && !firstMessage.visibility.equalsIgnoreCase("private")) { + Pattern pattern = Helper.statusIdInUrl; + Matcher matcher = pattern.matcher(firstMessage.uri); + action_remote.setVisible(matcher.find()); + } else { + action_remote.setVisible(false); + } + } return true; } @@ -151,8 +177,53 @@ public class ContextActivity extends BaseActivity { ((FragmentMastodonContext) currentFragment).refresh(); } invalidateOptionsMenu(); + } else if (item.getItemId() == R.id.action_remote) { + + if (firstMessage == null) { + Toasty.warning(ContextActivity.this, getString(R.string.toast_try_later), Toasty.LENGTH_SHORT).show(); + return true; + } + if (firstMessage.account.acct != null) { + String[] splitAcct = firstMessage.account.acct.split("@"); + String instance; + if (splitAcct.length > 1) { + instance = splitAcct[1]; + } else { + Toasty.info(ContextActivity.this, getString(R.string.toast_on_your_instance), Toasty.LENGTH_SHORT).show(); + return true; + } + Pattern pattern = Helper.statusIdInUrl; + Matcher matcher = pattern.matcher(firstMessage.uri); + String remoteId = null; + if (matcher.find()) { + remoteId = matcher.group(1); + } + if (remoteId != null) { + StatusesVM statusesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class); + statusesVM.getStatus(instance, null, remoteId).observe(ContextActivity.this, status -> { + if (status != null) { + Intent intentContext = new Intent(ContextActivity.this, ContextActivity.class); + intentContext.putExtra(Helper.ARG_STATUS, status); + intentContext.putExtra(Helper.ARG_REMOTE_INSTANCE, instance); + intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intentContext); + } else { + Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); + } + }); + } else { + Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); + } + } else { + Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); + } } return true; } + @Override + public void get(Status status) { + firstMessage = status; + invalidateOptionsMenu(); + } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/activities/HashTagActivity.java b/app/src/main/java/app/fedilab/android/activities/HashTagActivity.java index d2b5d1c9..7bf49af5 100644 --- a/app/src/main/java/app/fedilab/android/activities/HashTagActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/HashTagActivity.java @@ -26,6 +26,7 @@ import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -56,11 +57,15 @@ public class HashTagActivity extends BaseActivity { public static int position; private String tag; - private boolean pinnedTag; - private boolean followedTag; - private boolean mutedTag; + private String stripTag; + private Boolean pinnedTag; + private Boolean followedTag; + private Boolean mutedTag; private TagVM tagVM; private Filter fedilabFilter; + private Filter.KeywordsAttributes keyword; + private PinnedTimeline pinnedTimeline; + private Pinned pinned; @Override protected void onCreate(Bundle savedInstanceState) { @@ -75,9 +80,10 @@ public class HashTagActivity extends BaseActivity { } if (tag == null) finish(); - pinnedTag = false; - followedTag = false; - mutedTag = false; + pinnedTag = null; + followedTag = null; + mutedTag = null; + stripTag = tag.replaceAll("#", ""); setSupportActionBar(binding.toolbar); ActionBar actionBar = getSupportActionBar(); //Remove title @@ -91,7 +97,7 @@ public class HashTagActivity extends BaseActivity { } tagVM = new ViewModelProvider(HashTagActivity.this).get(TagVM.class); - tagVM.getTag(MainActivity.currentInstance, MainActivity.currentToken, tag).observe(this, returnedTag -> { + tagVM.getTag(MainActivity.currentInstance, MainActivity.currentToken, stripTag).observe(this, returnedTag -> { if (returnedTag != null) { followedTag = returnedTag.following; invalidateOptionsMenu(); @@ -100,19 +106,24 @@ public class HashTagActivity extends BaseActivity { ReorderVM reorderVM = new ViewModelProvider(HashTagActivity.this).get(ReorderVM.class); reorderVM.getAllPinned().observe(HashTagActivity.this, pinned -> { if (pinned != null) { + this.pinned = pinned; + pinnedTag = false; if (pinned.pinnedTimelines != null) { for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) { if (pinnedTimeline.tagTimeline != null) { - if (pinnedTimeline.tagTimeline.name.equalsIgnoreCase(tag)) { + if (pinnedTimeline.tagTimeline.name.equalsIgnoreCase(stripTag)) { + this.pinnedTimeline = pinnedTimeline; pinnedTag = true; - invalidateOptionsMenu(); + break; } } } + invalidateOptionsMenu(); } } }); if (MainActivity.filterFetched && MainActivity.mainFilters != null) { + mutedTag = false; for (Filter filter : MainActivity.mainFilters) { if (filter.title.equalsIgnoreCase(Helper.FEDILAB_MUTED_HASHTAGS)) { fedilabFilter = filter; @@ -120,17 +131,14 @@ public class HashTagActivity extends BaseActivity { for (Filter.KeywordsAttributes keywordsAttributes : filter.keywords) { if (fetch.equalsIgnoreCase(keywordsAttributes.keyword)) { mutedTag = true; + keyword = keywordsAttributes; invalidateOptionsMenu(); break; } } - mutedTag = false; - invalidateOptionsMenu(); - break; } } - } else { - mutedTag = true; + invalidateOptionsMenu(); } Bundle bundle = new Bundle(); @@ -141,12 +149,12 @@ public class HashTagActivity extends BaseActivity { Intent intentToot = new Intent(HashTagActivity.this, ComposeActivity.class); StatusDraft statusDraft = new StatusDraft(); Status status = new Status(); - status.text = "#" + tag; + status.text = "#" + stripTag; List statuses = new ArrayList<>(); statuses.add(status); statusDraft.statusDraftList = statuses; Bundle _b = new Bundle(); - _b.putSerializable(Helper.ARG_TAG_TIMELINE, statusDraft); + _b.putSerializable(Helper.ARG_STATUS_DRAFT, statusDraft); intentToot.putExtras(_b); startActivity(intentToot); }); @@ -158,89 +166,125 @@ public class HashTagActivity extends BaseActivity { finish(); return true; } else if (item.getItemId() == R.id.action_add_timeline) { - new Thread(() -> { - try { - Pinned pinned = new Pinned(HashTagActivity.this).getPinned(currentAccount); - boolean canBeAdded = true; - boolean update = true; - if (pinned == null) { - pinned = new Pinned(); - pinned.pinnedTimelines = new ArrayList<>(); - update = false; - } else { - for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) { - if (pinnedTimeline.type == Timeline.TimeLineEnum.TAG) { - if (pinnedTimeline.tagTimeline.name.compareTo(tag.trim()) == 0) { - canBeAdded = false; - } - } - } - } - if (!canBeAdded) { - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> Toasty.warning(HashTagActivity.this, getString(R.string.tags_already_stored), Toasty.LENGTH_SHORT).show(); - mainHandler.post(myRunnable); - return; - } - PinnedTimeline pinnedTimeline = new PinnedTimeline(); - pinnedTimeline.type = Timeline.TimeLineEnum.TAG; - pinnedTimeline.position = pinned.pinnedTimelines.size(); - pinnedTimeline.displayed = true; - TagTimeline tagTimeline = new TagTimeline(); - tagTimeline.name = tag.trim(); - tagTimeline.isNSFW = false; - tagTimeline.isART = false; - pinnedTimeline.tagTimeline = tagTimeline; - pinned.pinnedTimelines.add(pinnedTimeline); - if (update) { + + if (pinnedTag) { + AlertDialog.Builder unpinConfirm = new AlertDialog.Builder(HashTagActivity.this, Helper.dialogStyle()); + unpinConfirm.setMessage(getString(R.string.unpin_timeline_description)); + unpinConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + unpinConfirm.setPositiveButton(R.string.yes, (dialog, which) -> { + pinned.pinnedTimelines.remove(pinnedTimeline); + try { new Pinned(HashTagActivity.this).updatePinned(pinned); - } else { - new Pinned(HashTagActivity.this).insertPinned(pinned); + } catch (DBException e) { + e.printStackTrace(); } + pinnedTag = false; + invalidateOptionsMenu(); Bundle b = new Bundle(); b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); Intent intentBD = new Intent(Helper.BROADCAST_DATA); intentBD.putExtras(b); LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD); - pinnedTag = true; - invalidateOptionsMenu(); - } catch (DBException e) { - e.printStackTrace(); - } - }).start(); + dialog.dismiss(); + }); + unpinConfirm.show(); + } else { + new Thread(() -> { + try { + Pinned pinned = new Pinned(HashTagActivity.this).getPinned(currentAccount); + boolean canBeAdded = true; + boolean update = true; + if (pinned == null) { + pinned = new Pinned(); + pinned.pinnedTimelines = new ArrayList<>(); + update = false; + } else { + for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) { + if (pinnedTimeline.type == Timeline.TimeLineEnum.TAG) { + if (pinnedTimeline.tagTimeline.name.compareTo(stripTag.trim()) == 0) { + canBeAdded = false; + } + } + } + } + if (!canBeAdded) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> Toasty.warning(HashTagActivity.this, getString(R.string.tags_already_stored), Toasty.LENGTH_SHORT).show(); + mainHandler.post(myRunnable); + return; + } + pinnedTimeline = new PinnedTimeline(); + pinnedTimeline.type = Timeline.TimeLineEnum.TAG; + pinnedTimeline.position = pinned.pinnedTimelines.size(); + pinnedTimeline.displayed = true; + TagTimeline tagTimeline = new TagTimeline(); + tagTimeline.name = stripTag.trim(); + tagTimeline.isNSFW = false; + tagTimeline.isART = false; + pinnedTimeline.tagTimeline = tagTimeline; + pinned.pinnedTimelines.add(pinnedTimeline); + if (update) { + new Pinned(HashTagActivity.this).updatePinned(pinned); + } else { + new Pinned(HashTagActivity.this).insertPinned(pinned); + } + Bundle b = new Bundle(); + b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); + Intent intentBD = new Intent(Helper.BROADCAST_DATA); + intentBD.putExtras(b); + LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD); + pinnedTag = true; + invalidateOptionsMenu(); + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); + } } else if (item.getItemId() == R.id.action_follow_tag) { - tagVM.follow(MainActivity.currentInstance, MainActivity.currentToken, tag).observe(this, returnedTag -> { - if (returnedTag != null) { - followedTag = returnedTag.following; - invalidateOptionsMenu(); - } - }); + if (!followedTag) { + tagVM.follow(MainActivity.currentInstance, MainActivity.currentToken, stripTag).observe(this, returnedTag -> { + if (returnedTag != null) { + followedTag = returnedTag.following; + invalidateOptionsMenu(); + } + }); + } else { + tagVM.unfollow(MainActivity.currentInstance, MainActivity.currentToken, stripTag).observe(this, returnedTag -> { + if (returnedTag != null) { + followedTag = returnedTag.following; + invalidateOptionsMenu(); + } + }); + } } else if (item.getItemId() == R.id.action_mute) { - if (MainActivity.mainFilters == null || fedilabFilter == null) { - MainActivity.mainFilters = new ArrayList<>(); - Filter.FilterParams filterParams = new Filter.FilterParams(); - filterParams.title = Helper.FEDILAB_MUTED_HASHTAGS; - filterParams.filter_action = "hide"; - filterParams.context = new ArrayList<>(); - filterParams.context.add("home"); - filterParams.context.add("public"); - filterParams.context.add("thread"); - filterParams.context.add("account"); - String finalTag = tag; - FiltersVM filtersVM = new ViewModelProvider(HashTagActivity.this).get(FiltersVM.class); - filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams) - .observe(HashTagActivity.this, filter -> { - if (filter != null) { - MainActivity.mainFilters.add(filter); - mutedTag = false; - fedilabFilter = filter; - muteTags(); - invalidateOptionsMenu(); - } - }); + if (!mutedTag) { + if (MainActivity.mainFilters == null || fedilabFilter == null) { + MainActivity.mainFilters = new ArrayList<>(); + Filter.FilterParams filterParams = new Filter.FilterParams(); + filterParams.title = Helper.FEDILAB_MUTED_HASHTAGS; + filterParams.filter_action = "hide"; + filterParams.context = new ArrayList<>(); + filterParams.context.add("home"); + filterParams.context.add("public"); + filterParams.context.add("thread"); + filterParams.context.add("account"); + FiltersVM filtersVM = new ViewModelProvider(HashTagActivity.this).get(FiltersVM.class); + filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams) + .observe(HashTagActivity.this, filter -> { + if (filter != null) { + MainActivity.mainFilters.add(filter); + mutedTag = false; + fedilabFilter = filter; + muteTags(); + invalidateOptionsMenu(); + } + }); + } else { + muteTags(); + } } else { - muteTags(); + unmuteTags(); } } @@ -249,6 +293,24 @@ public class HashTagActivity extends BaseActivity { } + private void unmuteTags() { + String search = tag.startsWith("#") ? tag : "#" + tag; + for (Filter.KeywordsAttributes keywordsAttributes : fedilabFilter.keywords) { + if (search.equalsIgnoreCase(keywordsAttributes.keyword)) { + keyword = keywordsAttributes; + break; + } + } + if (keyword != null && keyword.id != null) { + FiltersVM filtersVM = new ViewModelProvider(HashTagActivity.this).get(FiltersVM.class); + filtersVM.removeKeyword(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, keyword.id); + fedilabFilter.keywords.remove(keyword); + mutedTag = false; + invalidateOptionsMenu(); + } + } + + private void muteTags() { Filter.FilterParams filterParams = new Filter.FilterParams(); filterParams.id = fedilabFilter.id; @@ -261,6 +323,7 @@ public class HashTagActivity extends BaseActivity { FiltersVM filtersVM = new ViewModelProvider(HashTagActivity.this).get(FiltersVM.class); filtersVM.editFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams) .observe(HashTagActivity.this, filter -> { + fedilabFilter = filter; mutedTag = true; invalidateOptionsMenu(); }); @@ -272,13 +335,42 @@ public class HashTagActivity extends BaseActivity { MenuItem pin = menu.findItem(R.id.action_add_timeline); MenuItem follow = menu.findItem(R.id.action_follow_tag); MenuItem mute = menu.findItem(R.id.action_mute); - if (pinnedTag && pin != null) { + if (pinnedTag != null) { + pin.setVisible(true); + if (pinnedTag) { + pin.setIcon(R.drawable.tag_pin_off); + pin.setTitle(getString(R.string.unpin_tag)); + } else { + pin.setTitle(getString(R.string.unpin_tag)); + pin.setIcon(R.drawable.tag_pin); + } + } else { pin.setVisible(false); } - if (followedTag && follow != null) { + if (followedTag != null) { + follow.setVisible(true); + if (followedTag) { + follow.setTitle(getString(R.string.unfollow_tag)); + follow.setIcon(R.drawable.tag_unfollow); + } else { + follow.setTitle(getString(R.string.follow_tag)); + follow.setIcon(R.drawable.tag_follow); + } + } else { follow.setVisible(false); } - mute.setVisible(!mutedTag); + if (mutedTag != null) { + mute.setVisible(true); + if (mutedTag) { + mute.setTitle(getString(R.string.unmute_tag_action)); + mute.setIcon(R.drawable.tag_unmuted); + } else { + mute.setTitle(getString(R.string.mute_tag_action)); + mute.setIcon(R.drawable.tag_muted); + } + } else { + mute.setVisible(false); + } return super.onCreateOptionsMenu(menu); } diff --git a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java index d70c9339..4b91601b 100644 --- a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java @@ -58,7 +58,7 @@ public class LoginActivity extends BaseActivity { public static boolean requestedAdmin; @SuppressLint("ApplySharedPref") - public static void proceedLogin(Activity activity, Account account) { + public void proceedLogin(Activity activity, Account account) { new Thread(() -> { try { //update the database @@ -74,8 +74,8 @@ public class LoginActivity extends BaseActivity { //The user is now authenticated, it will be redirected to MainActivity Runnable myRunnable = () -> { Intent mainActivity = new Intent(activity, MainActivity.class); - activity.startActivity(mainActivity); - activity.finish(); + startActivity(mainActivity); + finish(); }; mainHandler.post(myRunnable); } catch (DBException e) { @@ -111,18 +111,23 @@ public class LoginActivity extends BaseActivity { //API call to retrieve account information for the new token AccountsVM accountsVM = new ViewModelProvider(LoginActivity.this).get(AccountsVM.class); accountsVM.getConnectedAccount(currentInstanceLogin, account.token).observe(LoginActivity.this, mastodonAccount -> { - account.mastodon_account = mastodonAccount; - account.user_id = mastodonAccount.id; - //We check if user have really moderator rights - if (requestedAdmin) { - AdminVM adminVM = new ViewModelProvider(LoginActivity.this).get(AdminVM.class); - adminVM.getAccount(account.instance, account.token, account.user_id).observe(LoginActivity.this, adminAccount -> { - account.admin = adminAccount != null; + if (mastodonAccount != null) { + account.mastodon_account = mastodonAccount; + account.user_id = mastodonAccount.id; + //We check if user have really moderator rights + if (requestedAdmin) { + AdminVM adminVM = new ViewModelProvider(LoginActivity.this).get(AdminVM.class); + adminVM.getAccount(account.instance, account.token, account.user_id).observe(LoginActivity.this, adminAccount -> { + account.admin = adminAccount != null; + proceedLogin(LoginActivity.this, account); + }); + } else { proceedLogin(LoginActivity.this, account); - }); + } } else { - proceedLogin(LoginActivity.this, account); + Toasty.error(LoginActivity.this, getString(R.string.toast_token), Toast.LENGTH_LONG).show(); } + }); } else { Toasty.error(LoginActivity.this, getString(R.string.toast_token), Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java index 5c34875e..79ac5c58 100644 --- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java @@ -188,6 +188,30 @@ public class ProfileActivity extends BaseActivity { LocalBroadcastManager.getInstance(ProfileActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); } + + private void updateViewWithNewData(Account account) { + if (account != null) { + if (account.role != null && account.role.highlighted) { + binding.accountRole.setText(account.role.name); + binding.accountRole.setVisibility(View.VISIBLE); + } + if (binding.accountTabLayout.getTabCount() > 2) { + TabLayout.Tab statusTab = binding.accountTabLayout.getTabAt(0); + TabLayout.Tab followingTab = binding.accountTabLayout.getTabAt(1); + TabLayout.Tab followerTab = binding.accountTabLayout.getTabAt(2); + if (statusTab != null) { + statusTab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count))); + } + if (followingTab != null) { + followingTab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count))); + } + if (followerTab != null) { + followerTab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count))); + } + } + } + } + private void initializeView(Account account) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ProfileActivity.this); if (account == null) { @@ -497,7 +521,11 @@ public class ProfileActivity extends BaseActivity { }); } }); - + if (accountInstance != null && !accountInstance.equalsIgnoreCase(MainActivity.currentInstance)) { + accountsVM.lookUpAccount(accountInstance, account.username).observe(ProfileActivity.this, this::updateViewWithNewData); + } else if (accountInstance != null && accountInstance.equalsIgnoreCase(MainActivity.currentInstance)) { + updateViewWithNewData(account); + } } @@ -875,16 +903,20 @@ public class ProfileActivity extends BaseActivity { if (relationship == null || !relationship.following) { accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false) .observe(ProfileActivity.this, newRelationShip -> { - relationship = newRelationShip; - updateAccount(); - if (isChecked) { - timelinesVM.addAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds).observe(ProfileActivity.this, success -> { - if (success == null || !success) { - Toasty.error(ProfileActivity.this, getString(R.string.toast_error_add_to_list), Toast.LENGTH_LONG).show(); - } - }); + if (newRelationShip != null) { + relationship = newRelationShip; + updateAccount(); + if (isChecked) { + timelinesVM.addAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds).observe(ProfileActivity.this, success -> { + if (success == null || !success) { + Toasty.error(ProfileActivity.this, getString(R.string.toast_error_add_to_list), Toast.LENGTH_LONG).show(); + } + }); + } else { + timelinesVM.deleteAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); + } } else { - timelinesVM.deleteAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); + Toasty.error(ProfileActivity.this, getString(R.string.toast_error_add_to_list), Toast.LENGTH_LONG).show(); } }); } else { diff --git a/app/src/main/java/app/fedilab/android/activities/SettingsActivity.java b/app/src/main/java/app/fedilab/android/activities/SettingsActivity.java new file mode 100644 index 00000000..a7eac52c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/SettingsActivity.java @@ -0,0 +1,57 @@ +package app.fedilab.android.activities; +/* 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 . */ + +import static androidx.navigation.ui.NavigationUI.setupActionBarWithNavController; + +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.ui.AppBarConfiguration; + +import app.fedilab.android.R; +import app.fedilab.android.databinding.ActivitySettingsBinding; + +public class SettingsActivity extends BaseBarActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app.fedilab.android.databinding.ActivitySettingsBinding binding = ActivitySettingsBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + NavController navController = Navigation.findNavController(this, R.id.fragment_container); + AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder().build(); + setupActionBarWithNavController(this, navController, appBarConfiguration); + } + + @Override + public boolean onSupportNavigateUp() { + NavController navController = Navigation.findNavController(this, R.id.fragment_container); + return navController.navigateUp() || super.onSupportNavigateUp(); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + NavController navController = Navigation.findNavController(this, R.id.fragment_container); + if (item.getItemId() == android.R.id.home && navController.getCurrentDestination() != null && navController.getCurrentDestination().getId() == R.id.FragmentSettingsCategories) { + finish(); + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/app/fedilab/android/activities/SettingsActivity.kt b/app/src/main/java/app/fedilab/android/activities/SettingsActivity.kt deleted file mode 100644 index 32edbad4..00000000 --- a/app/src/main/java/app/fedilab/android/activities/SettingsActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -package app.fedilab.android.activities -/* 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 . */ - -import android.os.Bundle -import android.view.MenuItem -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupActionBarWithNavController -import app.fedilab.android.R -import app.fedilab.android.databinding.ActivitySettingsBinding - -class SettingsActivity : BaseBarActivity() { - private lateinit var binding: ActivitySettingsBinding - private lateinit var appBarConfiguration: AppBarConfiguration - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - - binding = ActivitySettingsBinding.inflate(layoutInflater) - setContentView(binding.root) - val navController = findNavController(R.id.fragment_container) - appBarConfiguration = AppBarConfiguration.Builder().build() - setupActionBarWithNavController(navController, appBarConfiguration) - - } - - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.fragment_container) - return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val navController = findNavController(R.id.fragment_container) - if (item.itemId == android.R.id.home && navController.currentDestination?.id == R.id.FragmentSettingsCategories) { - finish() - } - return super.onOptionsItemSelected(item) - } - -} diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java index a2ec0d5f..398c43dc 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java @@ -105,6 +105,12 @@ public interface MastodonAccountsService { @Path("id") String id ); + //Get Account + @GET("accounts/lookup") + Call lookUpAccount( + @Query("acct") String acct + ); + //Get Account statuses @GET("accounts/{id}/statuses") Call> getAccountStatuses( diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java index 88216303..8a438bbc 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java @@ -96,7 +96,7 @@ public interface MastodonFiltersService { ); //Remove a keyword for a filter - @DELETE("filter_keywords/{id}") + @DELETE("filters/keywords/{id}") Call removeKeywordFilter( @Header("Authorization") String token, @Path("id") String id diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java index 7a2bd1f2..ae0b71ae 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java @@ -73,12 +73,40 @@ public class Account implements Serializable { public List fields; @SerializedName("suspended") public boolean suspended; + @SerializedName("limited") + public boolean limited; @SerializedName("discoverable") public boolean discoverable; + @SerializedName("group") + public boolean group; @SerializedName("mute_expires_at") public Date mute_expires_at; @SerializedName("moved") public Account moved; + @SerializedName("role") + public Role role; + + + public static class Role implements Serializable { + @SerializedName("id") + public String id; + @SerializedName("name") + public String name; + @SerializedName("color") + public String color; + @SerializedName("position") + public int position; + @SerializedName("permissions") + public int permissions; + @SerializedName("highlighted") + public boolean highlighted; + @SerializedName("created_at") + public Date created_at; + @SerializedName("updated_at") + public Date updated_at; + } + + public transient RelationShip relationShip; public synchronized Spannable getSpanDisplayName(Context context, WeakReference viewWeakReference) { diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/Account.java b/app/src/main/java/app/fedilab/android/client/entities/app/Account.java index 7564cc36..090c1b3d 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/Account.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/Account.java @@ -318,6 +318,23 @@ public class Account extends BaseAccount implements Serializable { } } + /** + * Returns last used account + * + * @return BaseAccount {@link BaseAccount} + */ + public List getLastUsedAccounts() throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + try { + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, null, null, null, null, Sqlite.COL_UPDATED_AT + " DESC", null); + return cursorToListUser(c); + } catch (Exception e) { + return null; + } + } + /** * Remove an account from db * diff --git a/app/src/main/java/app/fedilab/android/helper/CrossActionHelper.java b/app/src/main/java/app/fedilab/android/helper/CrossActionHelper.java index 40e62117..c9c85dbd 100644 --- a/app/src/main/java/app/fedilab/android/helper/CrossActionHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/CrossActionHelper.java @@ -439,6 +439,44 @@ public class CrossActionHelper { } + /** + * Fetch and federate the remote status + */ + public static void fetchStatusInRemoteInstance(@NonNull Context context, String url, String instance, Callback callback) { + + MastodonSearchService mastodonSearchService = init(context, instance); + new Thread(() -> { + Call resultsCall = mastodonSearchService.search(null, url, null, "statuses", null, null, null, null, null, null, null); + Results results = null; + if (resultsCall != null) { + try { + Response resultsResponse = resultsCall.execute(); + if (resultsResponse.isSuccessful()) { + + results = resultsResponse.body(); + if (results != null) { + if (results.statuses == null) { + results.statuses = new ArrayList<>(); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Results finalResults = results; + Runnable myRunnable = () -> { + if (finalResults != null && finalResults.statuses != null && finalResults.statuses.size() > 0) { + callback.federatedStatus(finalResults.statuses.get(0)); + } + }; + mainHandler.post(myRunnable); + + }).start(); + } + + /** * Fetch and federate the remote status */ diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 8b10a874..2b1aa30a 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -343,6 +343,8 @@ public class Helper { public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)"); public static final Pattern nitterIDPattern = Pattern.compile("/status/(\\d+)"); public static final Pattern emailPattern = Pattern.compile("(\\s+[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)"); + public static final Pattern statusIdInUrl = Pattern.compile("statuses/(\\w+)"); + /*public static final Pattern urlPattern = Pattern.compile( "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,10}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))", diff --git a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java index 9508f750..0d5b7bf3 100644 --- a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java @@ -53,6 +53,11 @@ import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + import java.io.IOException; import java.lang.ref.WeakReference; import java.net.MalformedURLException; @@ -127,6 +132,19 @@ public class SpannableHelper { if (text == null) { return null; } + Document htmlContent = Jsoup.parse(text); + Elements mentionElements = htmlContent.select("a.mention"); + //We keep a reference to mentions + HashMap mentionsMap = new HashMap<>(); + if (mentionElements.size() > 0) { + for (int i = 0; i < mentionElements.size(); i++) { + Element mentionElement = mentionElements.get(i); + String href = mentionElement.attr("href"); + String mention = mentionElement.text(); + mentionsMap.put(mention, href); + } + } + text = text.replaceAll("((<\\s?p\\s?>|<\\s?br\\s?\\/?>)>(((?!([<])).)*))", "$2
$3
"); Pattern imgPattern = Pattern.compile("]*src=\"([^\"]+)\"[^>]*>"); Matcher matcherImg = imgPattern.matcher(text); @@ -178,7 +196,7 @@ public class SpannableHelper { content.removeSpan(span); } //Make tags, mentions, groups - interaction(context, content, status, mentionList, forceMentions); + interaction(context, content, status, mentionList, forceMentions, mentionsMap); //Make all links linkify(context, content, urlDetails); linkifyURL(context, content, urlDetails); @@ -759,7 +777,7 @@ public class SpannableHelper { } } - private static void interaction(Context context, Spannable content, Status status, List mentions, boolean forceMentions) { + private static void interaction(Context context, Spannable content, Status status, List mentions, boolean forceMentions, HashMap mentionsMap) { // --- For all patterns defined in Helper class --- for (Map.Entry entry : Helper.patternHashMap.entrySet()) { Helper.PatternType patternType = entry.getKey(); @@ -837,8 +855,9 @@ public class SpannableHelper { intent = new Intent(context, ProfileActivity.class); b = new Bundle(); Mention targetedMention = null; + String acct = null; HashMap countUsername = new HashMap<>(); - + //Mentions is retrieved with associated Mentions array if (mentions != null) { for (Mention mention : mentions) { Integer count = countUsername.get(mention.username); @@ -861,11 +880,19 @@ public class SpannableHelper { break; } } + } else if (mentionsMap.containsKey(word.trim())) {//Mentions will be find through its URL + URL url; + try { + url = new URL(mentionsMap.get(word.trim())); + acct = word.trim() + "@" + url.getHost(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } } if (targetedMention != null) { b.putString(Helper.ARG_USER_ID, targetedMention.id); } else { - b.putString(Helper.ARG_MENTION, word.trim()); + b.putString(Helper.ARG_MENTION, acct != null ? acct : word.trim()); } intent.putExtras(b); diff --git a/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java b/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java index f8c7643e..46c90ca7 100644 --- a/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java +++ b/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java @@ -231,7 +231,9 @@ public class ComposeWorker extends Worker { if (statusResponse.isSuccessful()) { Status statusReply = statusResponse.body(); if (statusReply != null) { - StatusAdapter.sendAction(context, Helper.ARG_STATUS_POSTED, statusReply, null); + if (dataPost.statusEditId == null) { + StatusAdapter.sendAction(context, Helper.ARG_STATUS_POSTED, statusReply, null); + } } if (firstSendMessage == null && statusReply != null) { firstSendMessage = statusReply; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AccountsReplyAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AccountsReplyAdapter.java index 2b23a0e5..c0b378bf 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AccountsReplyAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AccountsReplyAdapter.java @@ -34,7 +34,6 @@ public class AccountsReplyAdapter extends RecyclerView.Adapter accounts, List checked) { this.accounts = accounts; this.checked = new boolean[checked.size()]; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java index c4efd535..9fdeb620 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java @@ -80,8 +80,9 @@ import java.lang.ref.WeakReference; import java.text.Normalizer; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -124,11 +125,52 @@ public class ComposeAdapter extends RecyclerView.Adapter ALPHA_TO_MORSE = new LinkedHashMap<>(); + for (int i = 0; i < ALPHA.length && i < MORSE.length; i++) { + ALPHA_TO_MORSE.put(MORSE[i], ALPHA[i]); + } + List MORSELIST = Arrays.asList(MORSE2); + MORSELIST.sort((s1, s2) -> s2.length() - s1.length()); + LinkedHashMap MORSE_TO_ALPHA = new LinkedHashMap<>(); + for (String s : MORSELIST) { + MORSE_TO_ALPHA.put(s, ALPHA_TO_MORSE.get(s)); + } + for (String morseCode : MORSELIST) { + if (MORSE_TO_ALPHA.containsKey(morseCode)) { + morseContent = morseContent.replaceAll(Pattern.quote(morseCode), MORSE_TO_ALPHA.get(morseCode)); + } + } + return morseContent; + } + private final List statusList; private final int TYPE_NORMAL = 0; private final BaseAccount account; @@ -142,6 +184,7 @@ public class ComposeAdapter extends RecyclerView.Adapter emojisList = new ArrayList<>(); public promptDraftListener promptDraftListener; private boolean unlisted_changed = false; + public static int currentCursorPosition; public ComposeAdapter(List statusList, int statusCount, BaseAccount account, app.fedilab.android.client.entities.api.Account mentionedAccount, String visibility, String editMessageId) { this.statusList = statusList; @@ -298,6 +341,7 @@ public class ComposeAdapter extends RecyclerView.Adapter ALPHA_TO_MORSE = new HashMap<>(); + LinkedHashMap ALPHA_TO_MORSE = new LinkedHashMap<>(); for (int i = 0; i < ALPHA.length && i < MORSE.length; i++) { ALPHA_TO_MORSE.put(ALPHA[i], MORSE[i]); } @@ -550,7 +594,6 @@ public class ComposeAdapter extends RecyclerView.Adapter { @@ -590,7 +633,7 @@ public class ComposeAdapter extends RecyclerView.Adapter //Used to write contact when composing public void updateContent(boolean checked, String acct) { - if (checked) { - if (!statusList.get(statusList.size() - 1).text.contains(acct)) - statusList.get(statusList.size() - 1).text = String.format("%s %s", acct, statusList.get(statusList.size() - 1).text); - } else { - statusList.get(statusList.size() - 1).text = statusList.get(statusList.size() - 1).text.replaceAll("\\s*" + acct, ""); + if (currentCursorPosition < statusList.size()) { + if (checked) { + if (statusList.get(currentCursorPosition).text == null) { + statusList.get(currentCursorPosition).text = ""; + } + if (!statusList.get(currentCursorPosition).text.contains(acct)) { + statusList.get(currentCursorPosition).text = String.format("@%s %s", acct, statusList.get(currentCursorPosition).text); + } + } else { + statusList.get(currentCursorPosition).text = statusList.get(currentCursorPosition).text.replaceAll("@" + acct, ""); + } + notifyItemChanged(currentCursorPosition); } - notifyItemChanged(statusList.size() - 1); } //Put cursor to the end after changing contacts @@ -1038,16 +1089,14 @@ public class ComposeAdapter extends RecyclerView.Adapter displayAttachments(holder, position, finalMediaPosition)); if (attachment.description == null || attachment.description.trim().isEmpty()) { composeAttachmentItemBinding.buttonDescription.setIconResource(R.drawable.ic_baseline_warning_24); - composeAttachmentItemBinding.buttonDescription.setStrokeColor(ThemeHelper.getNoDescriptionColorStateList(context)); - composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(context, R.color.no_description)); - Helper.changeDrawableColor(context, R.drawable.ic_baseline_warning_24, ContextCompat.getColor(context, R.color.no_description)); - composeAttachmentItemBinding.buttonDescription.setIconTint(ThemeHelper.getNoDescriptionColorStateList(context)); + composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(context, R.color.black)); + composeAttachmentItemBinding.buttonDescription.setIconTintResource(R.color.black); + composeAttachmentItemBinding.buttonDescription.setBackgroundTintList(ThemeHelper.getNoDescriptionColorStateList(context)); } else { - composeAttachmentItemBinding.buttonDescription.setIconTint(ThemeHelper.getHavingDescriptionColorStateList(context)); composeAttachmentItemBinding.buttonDescription.setIconResource(R.drawable.ic_baseline_check_circle_24); - composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(context, R.color.having_description)); - composeAttachmentItemBinding.buttonDescription.setStrokeColor(ThemeHelper.getHavingDescriptionColorStateList(context)); - Helper.changeDrawableColor(context, R.drawable.ic_baseline_check_circle_24, ContextCompat.getColor(context, R.color.having_description)); + composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(context, R.color.white)); + composeAttachmentItemBinding.buttonDescription.setIconTintResource(R.color.white); + composeAttachmentItemBinding.buttonDescription.setBackgroundTintList(ThemeHelper.getHavingDescriptionColorStateList(context)); } holder.binding.attachmentsList.addView(composeAttachmentItemBinding.getRoot()); mediaPosition++; @@ -1318,6 +1367,11 @@ public class ComposeAdapter extends RecyclerView.Adapter { + if (focused) { + currentCursorPosition = holder.getLayoutPosition(); + } + }); if (statusDraft.cursorPosition <= holder.binding.content.length()) { holder.binding.content.setSelection(statusDraft.cursorPosition); } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ConversationAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ConversationAdapter.java index c98b9062..5651ca47 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ConversationAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ConversationAdapter.java @@ -60,6 +60,7 @@ public class ConversationAdapter extends RecyclerView.Adapter conversations) { if (conversations == null) { @@ -194,7 +195,7 @@ public class ConversationAdapter extends RecyclerView.Adapter(holder.binding.spoiler), () -> notifyItemChanged(holder.getBindingAdapterPosition())), + new WeakReference<>(holder.binding.spoiler), () -> mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))), TextView.BufferType.SPANNABLE); } else { holder.binding.spoiler.setVisibility(View.GONE); @@ -204,7 +205,7 @@ public class ConversationAdapter extends RecyclerView.Adapter(holder.binding.statusContent), () -> notifyItemChanged(holder.getBindingAdapterPosition())), + new WeakReference<>(holder.binding.statusContent), () -> mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))), TextView.BufferType.SPANNABLE); //--- DATE --- holder.binding.lastMessageDate.setText(Helper.dateDiff(context, conversation.last_status.created_at)); @@ -224,6 +225,7 @@ public class ConversationAdapter extends RecyclerView.Adapter 0) { - holderStatus.bindingNotification.status.mediaContainer.setVisibility(View.VISIBLE); - } else { + if (!displayMedia) { + holderStatus.bindingNotification.status.attachmentsListContainer.setVisibility(View.GONE); holderStatus.bindingNotification.status.mediaContainer.setVisibility(View.GONE); } String title = ""; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 1bb6e761..41b29f4b 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -395,6 +395,7 @@ public class StatusAdapter extends RecyclerView.Adapter boolean confirmBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION), true); boolean fullAttachement = sharedpreferences.getBoolean(context.getString(R.string.SET_FULL_PREVIEW), false); boolean displayBookmark = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_BOOKMARK), true); + boolean displayTranslate = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_TRANSLATE), false); boolean displayCounters = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_COUNTER_FAV_BOOST), false); String loadMediaType = sharedpreferences.getString(context.getString(R.string.SET_LOAD_MEDIA_TYPE), "ALWAYS"); @@ -628,6 +629,11 @@ public class StatusAdapter extends RecyclerView.Adapter } else { holder.binding.actionButtonBookmark.setVisibility(View.GONE); } + if (displayTranslate) { + holder.binding.actionButtonTranslate.setVisibility(View.VISIBLE); + } else { + holder.binding.actionButtonTranslate.setVisibility(View.GONE); + } //--- ACTIONS --- holder.binding.actionButtonBookmark.setChecked(statusToDeal.bookmarked); //---> BOOKMARK/UNBOOKMARK @@ -635,6 +641,9 @@ public class StatusAdapter extends RecyclerView.Adapter CrossActionHelper.doCrossAction(context, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, null, statusToDeal); return true; }); + holder.binding.actionButtonTranslate.setOnClickListener(v -> { + translate(context, statusToDeal, holder, adapter); + }); holder.binding.actionButtonBookmark.setOnClickListener(v -> { if (remote) { Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); @@ -887,6 +896,11 @@ public class StatusAdapter extends RecyclerView.Adapter holder.binding.actionButtonReply.getLayoutParams().width = (int) (normalSize * scaleIcon); holder.binding.actionButtonReply.getLayoutParams().height = (int) (normalSize * scaleIcon); holder.binding.actionButtonReply.requestLayout(); + + holder.binding.actionButtonTranslate.getLayoutParams().width = (int) (normalSize * scaleIcon); + holder.binding.actionButtonTranslate.getLayoutParams().height = (int) (normalSize * scaleIcon); + holder.binding.actionButtonTranslate.requestLayout(); + holder.binding.actionButtonBoost.setImageSize((int) (normalSize * scaleIcon)); holder.binding.actionButtonFavorite.setImageSize((int) (normalSize * scaleIcon)); holder.binding.actionButtonBookmark.setImageSize((int) (normalSize * scaleIcon)); @@ -1453,25 +1467,27 @@ public class StatusAdapter extends RecyclerView.Adapter } return; } - if (context instanceof ContextActivity) { + if (context instanceof ContextActivity && !remote) { Bundle bundle = new Bundle(); bundle.putSerializable(Helper.ARG_STATUS, statusToDeal); Fragment fragment = Helper.addFragment(((AppCompatActivity) context).getSupportFragmentManager(), R.id.nav_host_fragment_content_main, new FragmentMastodonContext(), bundle, null, FragmentMastodonContext.class.getName()); ((ContextActivity) context).setCurrentFragment((FragmentMastodonContext) fragment); } else { if (remote) { - Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); - searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1) - .observe((LifecycleOwner) context, results -> { - if (results != null && results.statuses != null && results.statuses.size() > 0) { - Status fetchedStatus = results.statuses.get(0); - Intent intent = new Intent(context, ContextActivity.class); - intent.putExtra(Helper.ARG_STATUS, fetchedStatus); - context.startActivity(intent); - } else { - Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show(); - } - }); + if (!(context instanceof ContextActivity)) { //We are not already checking a remote conversation + Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); + searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1) + .observe((LifecycleOwner) context, results -> { + if (results != null && results.statuses != null && results.statuses.size() > 0) { + Status fetchedStatus = results.statuses.get(0); + Intent intent = new Intent(context, ContextActivity.class); + intent.putExtra(Helper.ARG_STATUS, fetchedStatus); + context.startActivity(intent); + } else { + Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show(); + } + }); + } } else { Intent intent = new Intent(context, ContextActivity.class); intent.putExtra(Helper.ARG_STATUS, statusToDeal); @@ -1674,41 +1690,7 @@ public class StatusAdapter extends RecyclerView.Adapter })); builderInner.show(); } else if (itemId == R.id.action_translate) { - MyTransL.translatorEngine et = MyTransL.translatorEngine.LIBRETRANSLATE; - final MyTransL myTransL = MyTransL.getInstance(et); - myTransL.setObfuscation(true); - Params params = new Params(); - params.setSplit_sentences(false); - params.setFormat(Params.fType.TEXT); - params.setSource_lang("auto"); - myTransL.setLibretranslateDomain("translate.fedilab.app"); - String statusToTranslate; - String translate = sharedpreferences.getString(context.getString(R.string.SET_LIVE_TRANSLATE), MyTransL.getLocale()); - if (translate != null && translate.equalsIgnoreCase("default")) { - translate = MyTransL.getLocale(); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - statusToTranslate = Html.fromHtml(statusToDeal.content, Html.FROM_HTML_MODE_LEGACY).toString(); - else - statusToTranslate = Html.fromHtml(statusToDeal.content).toString(); - myTransL.translate(statusToTranslate, translate, params, new Results() { - @Override - public void onSuccess(Translate translate) { - if (translate.getTranslatedContent() != null) { - statusToDeal.translationShown = true; - statusToDeal.translationContent = translate.getTranslatedContent(); - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); - } else { - Toasty.error(context, context.getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFail(HttpsConnectionException httpsConnectionException) { - - } - }); + translate(context, statusToDeal, holder, adapter); return true; } else if (itemId == R.id.action_report) { Intent intent = new Intent(context, ReportActivity.class); @@ -1916,6 +1898,56 @@ public class StatusAdapter extends RecyclerView.Adapter } + private static void translate(Context context, Status statusToDeal, + StatusViewHolder holder, + RecyclerView.Adapter adapter) { + String statusToTranslate; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + statusToTranslate = Html.fromHtml(statusToDeal.content, Html.FROM_HTML_MODE_LEGACY).toString(); + else + statusToTranslate = Html.fromHtml(statusToDeal.content).toString(); + + int countMorseChar = ComposeAdapter.countMorseChar(statusToTranslate); + if (countMorseChar < 4) { + MyTransL.translatorEngine et = MyTransL.translatorEngine.LIBRETRANSLATE; + final MyTransL myTransL = MyTransL.getInstance(et); + myTransL.setObfuscation(true); + Params params = new Params(); + params.setSplit_sentences(false); + params.setFormat(Params.fType.TEXT); + params.setSource_lang("auto"); + myTransL.setLibretranslateDomain("translate.fedilab.app"); + + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + String translate = sharedpreferences.getString(context.getString(R.string.SET_LIVE_TRANSLATE), MyTransL.getLocale()); + if (translate != null && translate.equalsIgnoreCase("default")) { + translate = MyTransL.getLocale(); + } + + + myTransL.translate(statusToTranslate, translate, params, new Results() { + @Override + public void onSuccess(Translate translate) { + if (translate.getTranslatedContent() != null) { + statusToDeal.translationShown = true; + statusToDeal.translationContent = translate.getTranslatedContent(); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } else { + Toasty.error(context, context.getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFail(HttpsConnectionException httpsConnectionException) { + + } + }); + } else { + statusToDeal.translationShown = true; + statusToDeal.translationContent = ComposeAdapter.morseToText(statusToTranslate); + adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + } + } private static void loadAndAddAttachment(Context context, LayoutMediaBinding layoutMediaBinding, StatusViewHolder holder, @@ -2205,6 +2237,7 @@ public class StatusAdapter extends RecyclerView.Adapter Helper.changeDrawableColor(context, R.drawable.ic_person, theme_icons_color); Helper.changeDrawableColor(context, R.drawable.ic_bot, theme_icons_color); Helper.changeDrawableColor(context, R.drawable.ic_round_reply_24, theme_icons_color); + Helper.changeDrawableColor(context, holder.binding.actionButtonTranslate, theme_icons_color); holder.binding.actionButtonFavorite.setInActiveImageTintColor(theme_icons_color); holder.binding.actionButtonBookmark.setInActiveImageTintColor(theme_icons_color); holder.binding.actionButtonBoost.setInActiveImageTintColor(theme_icons_color); @@ -2265,8 +2298,8 @@ public class StatusAdapter extends RecyclerView.Adapter StatusViewHolder holder = (StatusViewHolder) viewHolder; SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); if (sharedpreferences.getBoolean(context.getString(R.string.SET_CARDVIEW), false)) { - holder.binding.cardviewContainer.setCardElevation(Helper.convertDpToPixel(5, context)); - holder.binding.dividerCard.setVisibility(View.GONE); + holder.bindingFilteredHide.cardviewContainer.setCardElevation(Helper.convertDpToPixel(5, context)); + holder.bindingFilteredHide.dividerCard.setVisibility(View.GONE); } if (status.isFetchMore && fetchMoreCallBack != null) { holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java new file mode 100644 index 00000000..b8be1e3c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java @@ -0,0 +1,176 @@ +package app.fedilab.android.ui.fragment.settings; +/* 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 . */ + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import app.fedilab.android.R; +import app.fedilab.android.helper.SettingsStorage; +import es.dmoral.toasty.Toasty; + +public class FragmentSettingsCategories extends PreferenceFragmentCompat { + + private static final int REQUEST_CODE = 5412; + private static final int PICKUP_FILE = 452; + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + addPreferencesFromResource(R.xml.pref_categories); + + + Preference pref_category_key_account = findPreference(getString(R.string.pref_category_key_account)); + if (pref_category_key_account != null) { + pref_category_key_account.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToAccount()); + return false; + }); + } + + Preference pref_category_key_timeline = findPreference(getString(R.string.pref_category_key_timeline)); + if (pref_category_key_timeline != null) { + pref_category_key_timeline.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToTimelines()); + return false; + }); + } + + Preference pref_category_key_notifications = findPreference(getString(R.string.pref_category_key_notifications)); + if (pref_category_key_notifications != null) { + pref_category_key_notifications.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToNotifications()); + return false; + }); + } + + Preference pref_category_key_interface = findPreference(getString(R.string.pref_category_key_interface)); + if (pref_category_key_interface != null) { + pref_category_key_interface.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToInterface()); + return false; + }); + } + + Preference pref_category_key_compose = findPreference(getString(R.string.pref_category_key_compose)); + if (pref_category_key_compose != null) { + pref_category_key_compose.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToCompose()); + return false; + }); + } + + Preference pref_category_key_languages = findPreference(getString(R.string.pref_category_key_languages)); + if (pref_category_key_languages != null) { + pref_category_key_languages.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToLanguage()); + return false; + }); + } + + Preference pref_category_key_privacy = findPreference(getString(R.string.pref_category_key_privacy)); + if (pref_category_key_privacy != null) { + pref_category_key_privacy.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToPrivacy()); + return false; + }); + } + + Preference pref_category_key_theming = findPreference(getString(R.string.pref_category_key_theming)); + if (pref_category_key_theming != null) { + pref_category_key_theming.setOnPreferenceClickListener(preference -> { + NavController navController = Navigation.findNavController(requireActivity(), R.id.fragment_container); + navController.navigate(FragmentSettingsCategoriesDirections.Companion.categoriesToTheming()); + return false; + }); + } + ActivityResultLauncher permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + } else { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); + } + }); + + Preference pref_export_settings = findPreference(getString(R.string.pref_export_settings)); + if (pref_export_settings != null) { + pref_export_settings.setOnPreferenceClickListener(preference -> { + permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); + return false; + }); + } + + Preference pref_import_settings = findPreference(getString(R.string.pref_import_settings)); + if (pref_import_settings != null) { + pref_import_settings.setOnPreferenceClickListener(preference -> { + Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); + openFileIntent.setType("text/plain"); + String[] mimeTypes = new String[]{"text/plain"}; + openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + startActivityForResult( + Intent.createChooser( + openFileIntent, + getString(R.string.load_settings)), PICKUP_FILE); + return false; + }); + } + } + + @SuppressWarnings("deprecation") + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (resultCode == Activity.RESULT_OK && requestCode == PICKUP_FILE) { + boolean result = data != null && SettingsStorage.loadSharedPreferencesFromFile(requireActivity(), data.getData()); + if (result) { + Toasty.success(requireActivity(), getString(R.string.data_import_settings_success), Toasty.LENGTH_LONG).show(); + } else { + Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + } + } + } + + @SuppressWarnings("deprecation") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == REQUEST_CODE) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + } else { + Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); + } + } + } +} diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.kt b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.kt deleted file mode 100644 index 4be8b730..00000000 --- a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.kt +++ /dev/null @@ -1,138 +0,0 @@ -package app.fedilab.android.ui.fragment.settings -/* 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 . */ - -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.navigation.fragment.findNavController -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import app.fedilab.android.BaseMainActivity.currentAccount -import app.fedilab.android.R -import app.fedilab.android.helper.SettingsStorage -import es.dmoral.toasty.Toasty - - -class FragmentSettingsCategories : PreferenceFragmentCompat() { - - private val REQUEST_CODE = 5412 - private val PICKUP_FILE = 452 - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.pref_categories, rootKey) - - findPreference(getString(R.string.pref_category_key_account))?.setOnPreferenceClickListener { - - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToAccount()) - - false - } - - findPreference(getString(R.string.pref_category_key_timeline))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToTimelines()) - false - } - - findPreference(getString(R.string.pref_category_key_notifications))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToNotifications()) - false - } - - findPreference(getString(R.string.pref_category_key_interface))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToInterface()) - false - } - - findPreference(getString(R.string.pref_category_key_compose))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToCompose()) - false - } - - findPreference(getString(R.string.pref_category_key_privacy))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToPrivacy()) - false - } - - findPreference(getString(R.string.pref_category_key_theming))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToTheming()) - false - } - @Suppress("DEPRECATION") val permissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - SettingsStorage.saveSharedPreferencesToFile(context) - } else { - requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_CODE) - } - } - findPreference(getString(R.string.pref_export_settings))?.setOnPreferenceClickListener { - permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - false - } - - findPreference(getString(R.string.pref_import_settings))?.setOnPreferenceClickListener { - val openFileIntent = Intent(Intent.ACTION_OPEN_DOCUMENT) - openFileIntent.addCategory(Intent.CATEGORY_OPENABLE) - openFileIntent.type = "text/plain" - val mimeTypes = arrayOf("text/plain") - openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) - - startActivityForResult( - Intent.createChooser( - openFileIntent, - getString(R.string.load_settings)), PICKUP_FILE) - false - } - - val adminPreference = findPreference(getString(R.string.pref_category_key_administration)) - adminPreference?.isVisible = currentAccount != null && currentAccount.admin - adminPreference?.setOnPreferenceClickListener { false } - - findPreference(getString(R.string.pref_category_key_languages))?.setOnPreferenceClickListener { - findNavController().navigate(FragmentSettingsCategoriesDirections.categoriesToLanguage()) - false - } - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK && requestCode == PICKUP_FILE) { - val result = SettingsStorage.loadSharedPreferencesFromFile(context, data?.data) - if (result) { - activity?.let { Toasty.success(it, getString(R.string.data_import_settings_success), Toasty.LENGTH_LONG).show() } - } else { - activity?.let { Toasty.error(it, getString(R.string.toast_error), Toasty.LENGTH_LONG).show() } - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - REQUEST_CODE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - SettingsStorage.saveSharedPreferencesToFile(context) - } else { - Toast.makeText(context, getString(R.string.permission_missing), Toast.LENGTH_SHORT).show() - } - else -> {} - } - } -} diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java index b3fd472d..ffa1d129 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java @@ -34,9 +34,9 @@ import androidx.recyclerview.widget.LinearLayoutManager; import java.util.ArrayList; import java.util.List; -import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.activities.ContextActivity; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.Context; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.app.Timeline; @@ -54,6 +54,8 @@ public class FragmentMastodonContext extends Fragment { private StatusesVM statusesVM; private List statuses; private StatusAdapter statusAdapter; + public FirstMessage firstMessage; + //Handle actions that can be done in other fragments private final BroadcastReceiver receive_action = new BroadcastReceiver() { @Override @@ -64,6 +66,7 @@ public class FragmentMastodonContext extends Fragment { String delete_statuses_for_user = b.getString(Helper.ARG_STATUS_ACCOUNT_ID_DELETED); Status status_to_delete = (Status) b.getSerializable(Helper.ARG_STATUS_DELETED); Status statusPosted = (Status) b.getSerializable(Helper.ARG_STATUS_POSTED); + Status status_to_update = (Status) b.getSerializable(Helper.ARG_STATUS_UPDATED); if (receivedStatus != null && statusAdapter != null) { int position = getPosition(receivedStatus); if (position >= 0) { @@ -95,6 +98,12 @@ public class FragmentMastodonContext extends Fragment { statuses.remove(position); statusAdapter.notifyItemRemoved(position); } + } else if (status_to_update != null && statusAdapter != null) { + int position = getPosition(status_to_update); + if (position >= 0) { + statuses.set(position, status_to_update); + statusAdapter.notifyItemChanged(position); + } } else if (statusPosted != null && statusAdapter != null) { if (requireActivity() instanceof ContextActivity) { int i = 0; @@ -116,8 +125,10 @@ public class FragmentMastodonContext extends Fragment { } }; private Status focusedStatus; + private String remote_instance; private Status firstStatus; private boolean pullToRefresh; + private String user_token, user_instance; /** * Return the position of the status in the ArrayList @@ -145,17 +156,26 @@ public class FragmentMastodonContext extends Fragment { pullToRefresh = false; if (getArguments() != null) { focusedStatus = (Status) getArguments().getSerializable(Helper.ARG_STATUS); + remote_instance = getArguments().getString(Helper.ARG_REMOTE_INSTANCE, null); + } + if (remote_instance != null) { + user_instance = remote_instance; + user_token = null; + } else { + user_instance = MainActivity.currentInstance; + user_token = MainActivity.currentToken; } if (focusedStatus == null) { getChildFragmentManager().beginTransaction().remove(this).commit(); } + binding = FragmentPaginationBinding.inflate(inflater, container, false); statusesVM = new ViewModelProvider(FragmentMastodonContext.this).get(StatusesVM.class); binding.recyclerView.setNestedScrollingEnabled(true); this.statuses = new ArrayList<>(); focusedStatus.isFocused = true; this.statuses.add(focusedStatus); - statusAdapter = new StatusAdapter(this.statuses, Timeline.TimeLineEnum.UNKNOWN, false, true, false); + statusAdapter = new StatusAdapter(this.statuses, Timeline.TimeLineEnum.UNKNOWN, false, true, remote_instance != null); binding.swipeContainer.setRefreshing(false); LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); binding.recyclerView.setLayoutManager(mLayoutManager); @@ -164,12 +184,12 @@ public class FragmentMastodonContext extends Fragment { if (this.statuses.size() > 0) { binding.swipeContainer.setRefreshing(true); pullToRefresh = true; - statusesVM.getContext(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id) + statusesVM.getContext(user_instance, user_token, focusedStatus.id) .observe(getViewLifecycleOwner(), this::initializeContextView); } }); if (focusedStatus != null) { - statusesVM.getContext(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id) + statusesVM.getContext(user_instance, user_token, focusedStatus.id) .observe(getViewLifecycleOwner(), this::initializeContextView); } LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); @@ -196,7 +216,7 @@ public class FragmentMastodonContext extends Fragment { } else { id = focusedStatus.id; } - statusesVM.getContext(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, id) + statusesVM.getContext(user_instance, user_token, id) .observe(FragmentMastodonContext.this, this::initializeContextView); } } @@ -228,6 +248,10 @@ public class FragmentMastodonContext extends Fragment { } else { firstStatus = statuses.get(0); } + if (firstMessage != null) { + firstMessage.get(firstStatus); + } + int statusPosition = context.ancestors.size(); //Build the array of statuses statuses.addAll(0, context.ancestors); @@ -250,4 +274,8 @@ public class FragmentMastodonContext extends Fragment { super.onDestroyView(); } + + public interface FirstMessage { + void get(Status status); + } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonConversation.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonConversation.java index ded9e0af..2b59c69f 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonConversation.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonConversation.java @@ -28,6 +28,7 @@ import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; import java.util.ArrayList; import java.util.List; @@ -262,6 +263,11 @@ public class FragmentMastodonConversation extends Fragment implements Conversati if (binding == null || !isAdded() || getActivity() == null) { return; } + + RecyclerView.ItemAnimator animator = binding.recyclerView.getItemAnimator(); + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); + } binding.loader.setVisibility(View.GONE); binding.noAction.setVisibility(View.GONE); binding.swipeContainer.setRefreshing(false); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java index 0bef27f8..dcedecff 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java @@ -32,6 +32,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; import com.google.gson.annotations.SerializedName; @@ -253,6 +254,10 @@ public class FragmentMastodonNotification extends Fragment implements Notificati if (binding == null || !isAdded() || getActivity() == null) { return; } + RecyclerView.ItemAnimator animator = binding.recyclerView.getItemAnimator(); + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); + } binding.loader.setVisibility(View.GONE); binding.noAction.setVisibility(View.GONE); binding.swipeContainer.setRefreshing(false); @@ -345,6 +350,9 @@ public class FragmentMastodonNotification extends Fragment implements Notificati route(null, false); } } + if (notificationList != null && notificationList.size() > 0) { + route(FragmentMastodonTimeline.DIRECTION.FETCH_NEW, true); + } } /** diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index 23651375..1ae41045 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -39,6 +39,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; import java.util.ArrayList; import java.util.List; @@ -361,7 +362,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (timelineType != null) { slug = timelineType != Timeline.TimeLineEnum.ART ? timelineType.getValue() + (ident != null ? "|" + ident : "") : Timeline.TimeLineEnum.TAG.getValue() + (ident != null ? "|" + ident : ""); } - LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); binding = FragmentPaginationBinding.inflate(inflater, container, false); return binding.getRoot(); @@ -539,6 +539,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (statusReport != null) { scrollToTop(); } + RecyclerView.ItemAnimator animator = binding.recyclerView.getItemAnimator(); + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); + } mLayoutManager = new LinearLayoutManager(requireActivity()); mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); binding.recyclerView.setLayoutManager(mLayoutManager); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java index 9b459689..8dfb7c30 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java @@ -212,9 +212,7 @@ public class FragmentNotificationContainer extends Fragment { } }); dialogBuilder.setOnDismissListener(dialogInterface -> doAction(changes.get(), excludedCategoriesList)); - dialogBuilder.setPositiveButton(R.string.close, (dialog, id) -> { - dialog.dismiss(); - }); + dialogBuilder.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss()); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.show(); }); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java index 1d640b2b..5d4e17cd 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java @@ -296,6 +296,36 @@ public class AccountsVM extends AndroidViewModel { return accountMutableLiveData; } + + /** + * @param acct The acct of the account + * @return {@link LiveData} containing an {@link Account} + */ + public LiveData lookUpAccount(@NonNull String instance, @NonNull String acct) { + accountMutableLiveData = new MutableLiveData<>(); + MastodonAccountsService mastodonAccountsService = init(instance); + new Thread(() -> { + Account account = null; + Call accountCall = mastodonAccountsService.lookUpAccount(acct); + if (accountCall != null) { + + try { + Response accountResponse = accountCall.execute(); + if (accountResponse.isSuccessful()) { + account = accountResponse.body(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Account finalAccount = account; + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> accountMutableLiveData.setValue(finalAccount); + mainHandler.post(myRunnable); + }).start(); + return accountMutableLiveData; + } + /** * @param id The id of the account * @return {@link LiveData} containing an {@link Account} diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java index b85f587a..ca73a5e8 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java @@ -212,4 +212,24 @@ public class FiltersVM extends AndroidViewModel { }).start(); } + + /** + * Remove a filter + * + * @param id ID of the filter + */ + public void removeKeyword(@NonNull String instance, String token, @NonNull String id) { + MastodonFiltersService mastodonAccountsService = initV2(instance); + new Thread(() -> { + Call removeFilterCall = mastodonAccountsService.removeKeywordFilter(token, id); + if (removeFilterCall != null) { + try { + removeFilterCall.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + } diff --git a/app/src/main/res/drawable/ic_baseline_check_circle_24.xml b/app/src/main/res/drawable/ic_baseline_check_circle_24.xml index 8c3b7cda..5f7978c0 100644 --- a/app/src/main/res/drawable/ic_baseline_check_circle_24.xml +++ b/app/src/main/res/drawable/ic_baseline_check_circle_24.xml @@ -1,7 +1,7 @@ + + diff --git a/app/src/main/res/drawable/ic_baseline_warning_24.xml b/app/src/main/res/drawable/ic_baseline_warning_24.xml index 779e6b4c..433eb0be 100644 --- a/app/src/main/res/drawable/ic_baseline_warning_24.xml +++ b/app/src/main/res/drawable/ic_baseline_warning_24.xml @@ -1,10 +1,10 @@ diff --git a/app/src/main/res/drawable/tag_follow.xml b/app/src/main/res/drawable/tag_follow.xml new file mode 100644 index 00000000..41568a65 --- /dev/null +++ b/app/src/main/res/drawable/tag_follow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag_muted.xml b/app/src/main/res/drawable/tag_muted.xml new file mode 100644 index 00000000..93125f55 --- /dev/null +++ b/app/src/main/res/drawable/tag_muted.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag_pin.xml b/app/src/main/res/drawable/tag_pin.xml new file mode 100644 index 00000000..4d3bb51d --- /dev/null +++ b/app/src/main/res/drawable/tag_pin.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag_pin_off.xml b/app/src/main/res/drawable/tag_pin_off.xml new file mode 100644 index 00000000..d8223d50 --- /dev/null +++ b/app/src/main/res/drawable/tag_pin_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag_unfollow.xml b/app/src/main/res/drawable/tag_unfollow.xml new file mode 100644 index 00000000..06b2a0e8 --- /dev/null +++ b/app/src/main/res/drawable/tag_unfollow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag_unmuted.xml b/app/src/main/res/drawable/tag_unmuted.xml new file mode 100644 index 00000000..6f15b551 --- /dev/null +++ b/app/src/main/res/drawable/tag_unmuted.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_conversation.xml b/app/src/main/res/layout/activity_conversation.xml index 21bb55f4..ac77201d 100644 --- a/app/src/main/res/layout/activity_conversation.xml +++ b/app/src/main/res/layout/activity_conversation.xml @@ -50,6 +50,8 @@ diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml index 523b390d..7f3b6ac4 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -142,17 +142,38 @@ app:layout_constraintTop_toBottomOf="@id/avatar_container" tools:text="@tools:sample/first_names" /> - + app:layout_constraintTop_toBottomOf="@id/account_dn"> + + + + + + + app:layout_constraintTop_toBottomOf="@+id/account_un_container"> + app:layout_constraintTop_toBottomOf="@id/preview" /> + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_hashtag.xml b/app/src/main/res/menu/menu_hashtag.xml index f35647d2..610310c3 100644 --- a/app/src/main/res/menu/menu_hashtag.xml +++ b/app/src/main/res/menu/menu_hashtag.xml @@ -3,17 +3,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0f48d25a..d2b43093 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -15,10 +15,10 @@ Média uložena Soubor: %1$s Heslo - Email + E-mail Účty Zprávy - Štítky + Tagy Uložit Instance Instance: mastodon.social @@ -53,21 +53,21 @@ Místní časová osa Ztlumení uživatelé Blokovaní uživatelé - Oznámení - Žádost o sledování + Upozornění + Žádosti o sledování Nastavení Poslat e-mail Naplánované zprávy Níže uvedené informace mohou popisovat uživatelský profil neúplně. Vložit smajlík Aplikace prozatím nenačetla uživatelské smajlíky. - Are you sure you want to logout @%1$s@%2$s? + Opravdu se chcete odhlásit od @%1$s@%2$s\? Žádné zprávy k zobrazení Přidat tuto zprávu k oblíbeným\? Odstranit tuto zprávu z oblíbených\? Boostnout tuto zprávu\? - Zrušit boost\? + Zrušit boost této zprávy\? Ztlumit Blokovat Nahlásit @@ -96,65 +96,62 @@ Záložky Přidat do záložek Odstranit záložku - Toot byl přidán do záložek! - Toot byl odstraněn ze záložek! + Status byl přidán do záložek! + Status byl odstraněn ze záložek! %d s %d m %d h %d d - %d second - %d seconds - %d seconds - %d seconds + %d sekunda + %d sekundy + %d sekund - %d minute - %d minutes - %d minutes - %d minutes + %d minuta + %d minuty + %d minut - %d hour - %d hours - %d hours - %d hours + %d hodina + %d hodiny + %d hodin - %d day - %d days - %d days - %d days + %d den + %d dny + %d dní Nastala chyba při výběru média! - Smazat médium? + Smazat médium\? Vaše zpráva je prázdná! Zpráva byla odeslána! - Citlivý obsah? + Citlivý obsah\? Žádné koncepty! Vyberte účet Vyberte účty - Odstranit koncept? + Odstranit koncept\? Popsat pro zrakově postižené Popis není dostupný! Release %1$s Vývojář: - Licence: + Licence: GNU GPL V3 - Zdrojový kód: + Zdrojový kód: Prohledat instance: Žádný účet k zobrazení - Není požadavek ke sledování + Žádný požadavek ke sledování Zprávy \n %1$s Sleduji \n %1$s - Sledující \n %1$s + Sledující +\n %1$s Odmítnout Žádné naplánované zprávy k zobrazení! @@ -167,14 +164,14 @@ %1$s je ztlumen do %2$s. \n Klikněte zde pro zrušení ztišení. Žádné upozornění k zobrazení - vás zmínil/a - wrote a new message - boostnul/a váš toot - si oblíbil/a váš toot + vás zmínil(a) + napsal(a) novou zprávu + boostnul(a) váš status + si oblíbil(a) váš status vás sleduje - asked to follow you - Smazat všechna oznámení? - Všechna oznámení byla smazána! + vás chce sledovat + Smazat všechna upozornění\? + Všechna upozornění byla smazána! Sledující @@ -191,7 +188,7 @@ Zpráva byla odstraněna z oblíbených! Oops! Došlo k chybě! Došlo k chybě! Instance nevrátila autorizační kód! - Tato doména není platná! + Doména instance se zdá být neplatná! Došlo k chybě při přepínání mezi účty! Při vyhledávání došlo k chybě! Nelze vykonat akci @@ -199,18 +196,18 @@ Počet zpráv pro jedno nahrání Zakázat GIF avatary - Oznámení v případě sledování - Oznámení v případě boostnutí vašeho tootu - Oznámení v případě oblíbení vašeho tootu - Oznámení v případě, že vás někdo zmíní - Oznámení po skončení ankety - Notify for new posts - Zobrazit potvrzení před boostnutí + Upozornit, když vás někdo začne sledovat + Upozornit, když někdo boostne váš status + Upozornit, když si někdo oblíbí váš status + Upozornit, když vás někdo zmíní + Upozornit, když skončí anketa + Upozornit na nové příspěvky + Zobrazit potvrzení před boostnutím Zobrazit potvrzení před oblíbením - Oznámení? - Tichá oznámení - NSFW prodleva (vteřiny, 0 znamená vypnuto) - Media Description timeout (seconds, 0 means off) + Upozornit\? + Tichá upozornění + Prodleva NSFW (v sekundách, 0 znamená vypnuto) + Timeout pro popis médií (v sekundách, 0 znamená vypnuto) Vlastní sdílení Vaše vlastní sdílecí URL… Zamknout účet @@ -231,7 +228,7 @@ Žlutá Bílá - Následovat + Sledovat Odblokovat Ztlumit Zrušit ztlumení @@ -239,7 +236,7 @@ Sleduje vás První písmeno velké v odpovědích Změnit velikost obrázků - Resize videos + Změnit velikost videa Mb @@ -260,26 +257,26 @@ Odstranit Seznamy - Jsi si jist/a, že chceš trvale odstranit tento seznam? + Opravdu chcete trvale smazat tento seznam\? Přidat do seznamu - Odstranit seznam + Smazat seznam Nový název seznamu - The account was added to the list! - You don\'t have any lists yet! + Účet byl přidán do seznamu! + Ještě nemáte žádný seznam! %1$s se přesunul do %2$s Média byla nahrána. Klikněte pro zobrazení. Proxy - Povolit proxy? + Povolit proxy\? Host Port Přihlašovací jméno Heslo Přidat podrobnosti zprávy při sdílení Podpořit aplikaci na Liberapay - Chyba v regulárním výrazu! - Časová osa nenalezena na této instanci! + V regulárním výrazu je chyba! + Na této instanci nebyly nalezeny žádné časové osy! Sledovat instanci Tuto instanci již sledujete! Partnerství @@ -292,13 +289,13 @@ Žádné filtry k zobrazení. Můžete vytvořit nový filtr klepnutím na tlačítko \"+\". Klíčové slovo nebo fráze Domovská časová osa - Veřejná časová osa - Oznámení + Veřejné časové osy + Upozornění Konverzace Velikost písmen ani varování o obsahu nebudou brána v potaz Zahodit místo skrytí Filtrované zprávy zmizí nezvratně i v případě, že je filtr později odstraněn - V případě, že klíčové slovo nebo fráze je pouze alfanumerické, filtr se uplatní pouze pokud odpovídá celému slovu + V případě, že klíčové slovo nebo fráze je pouze alfanumerické, filtr se uplatní, pouze pokud odpovídá celému slovu Celé slovo Kontext filtru Jeden nebo několik kontextů pro aplikaci filtru @@ -313,15 +310,15 @@ Nová zmínka Anketa skončila Záloha zpráv - New posts + Nové příspěvky Stahování médií Vybrat tón Zapnout rozvrh oznámení Určitě chcete zablokovat %s?\n\nJiž z této domény neuvidíte ve všech veřejných časových osách ani v oznámeních žádný obsah. Vaši sledující z této domény budou odstraněni. Zablokovat doménu Doména je blokována - Načítám vzdálený toot - Peertube instance + Načítám vzdálený status + Instance Peertube Použít Emoji One Informace Zobrazit náhled ve všech zprávách @@ -331,20 +328,20 @@ Ořezat zprávy delší než \'x\' řádků. 0 znamená vypnuto. Zobrazit více Zobrazit méně - Štítek již existuje! + Tag již existuje! Naplánovat boost - Boost je naplánováo! - Žádný naplánovaý boost k zobrazení! + Boost je naplánován! + Žádný naplánovaný boost k zobrazení! Otevřete nabídku Profilový obrázek Profilová hlavička Kontaktovat administrátora instance - MastoHost logo + Logo MastoHost Výběr emotikonů Ukázat celou konverzaci Uživatelský výběr emoji Favicon - Médium pro přidání popisu + Přidat k médiu popis (pro zrakově postižené) Nikdy 30 minut @@ -359,7 +356,7 @@ Pouze média Zobrazit NSFW Bot - Pixelfed instance + Instance Pixelfed Instance Mastodon Kterýkoliv Všechny @@ -368,7 +365,7 @@ Všechna slova (oddělená mezerami) Add some words to filter (space-separated) Změnit název sloupce - Misskey instance + Instance Misskey Populární Místní Kategorie @@ -376,19 +373,19 @@ Sdílet Zprávy (Server) Zprávy (Zařízení) - Časové osi + Časové osy Rozhraní Kontakty Při výběru zálohového souboru nastala chyba! Odhlásit účet Vše Kopírovat odkaz - volání http je blokováné aplikací - Seznam blokovaných domén + volání http je blokováno aplikací + Seznam blokovaných volání Odeslat Filtrovat časovou osu s hashtagy Žádné hashtagy - Připojit při sdílení URL obrázek + Při sdílení URL připojit obrázek Vytvořit anketu Volba %d @@ -396,7 +393,7 @@ Hotovo skončit po %s Hlasovat - Anketa, ve které jste hlasoval/a, skončila + Anketa, ve které jste hlasoval(a), skončila Vaše anketa skončila Kategorie Přesunout časovou osu @@ -404,14 +401,14 @@ Spravovat časové osy Seznam trvale smazán Sledovaná instance odstraněna - Připnuté značky odstraněny + Připnutý tag odstraněn Vrátit zpět - Hlavní časové linie mohou být pouze skryty! + Hlavní časové osy mohou být pouze skryty! Vždy označovat média jako citlivá - GNU instance - Forward tags in replies - Long press to store media - Spravovat štítky + Instance GNU + V odpovědích přeposílat tagy + K uložení média dlouze stlačte + Spravovat tagy Zobrazované jméno Emoji Text @@ -419,81 +416,84 @@ Štětec Zahodit Ukladáno… - Image Saved Successfully! + Obrázek byl úspěšně uložen! Nepodařilo se uložit obrázek Přidat položku ankety Ztišit konverzaci - Unmute conversation - The conversation is no longer muted! - The conversation is muted + Zrušit umlčení konverzace + Konverzace už není umlčená! + Konverzace je umlčená Základní Regionální Umění Aktivismus - Hrání + Hraní Technologie Furry Jídlo - Logo of the instance + Logo instance Připojte se k Mastodonu Choose an instance by picking up a category, then tap on a check button. - %1$s users - Confirm password - I agree to %1$s and %2$s + %1$s uživatelů + Potvrdit heslo + Souhlasím s %1$s a %2$s pravidla serveru podmínky užití Registrovat se - This instance works with invitations. Your account will need to be manually approved by an administrator before being usable. - Passwords don\'t match! - The email doesn\'t seem to be valid! - You will be sent a confirmation e-mail + Tato instance funguje na pozvánky. Aby se dal váš účet používat, musí ho ručně schválit administrátor. + Hesla nesouhlasí! + E-mail se zdá být neplatný! + Poslali jsme vám potvrzovací e-mail Použijte minimálně 8 znaků Heslo musí mít minimálně 8 znaků - Username should only contain letters, numbers and underscores + Uživatelské jméno smí obsahovat jen písmena, číslice a podtržítka Účet vytvořen! - 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 %1$s in the first field and click on Connect.\n\n - Important: If your instance required validation, you will receive an email once it is validated! - - Save the message in drafts? + Váš účet byl vytvořen! +\n +\n Nezapomeňte ověřit e-mail během příštích 48 hodin. +\n +\n Nyní se můžete připojit ke svému účtu napsáním %1$s do prvního pole a kliknutím na Připojit. +\n +\n Důležité: Pokud vaše instance vyžaduje ověření, po jeho provedení dostanete e-mailovou zprávu! + Uložit zprávu do konceptů\? Administrace - Reports - Unresolved + Hlášení + Nevyřešeno Remote - Active - Pending - Disabled - Suspended - Permissions - Disable + Aktivní + Čekající + Vypnuto + Pozastaveno + Oprávnění + Vypnout Silence - Account + Účet Undo silence - Undo disable - Suspend - Undo suspend - The application needs to access audio recording - Voice message + Zrušit vypnutí + Pozastavit + Zrušit pozastavení + Zvuk + Hlasová zpráva During the time slot, the app will send notifications. You can reverse (ie: silent) this time slot with the right spinner. - Previews will not be cropped in timelines + Náhledy nebudou v časových osách oříznuty Automatically insert a line break after the mention to capitalize the first letter - Allow content creators to share statuses to their RSS feeds - Compose + Umožňuje tvůrcům obsahu sdílet statusy do jejich kanálů RSS + Vytváření Select - Add an instance - Enable crash reports - If enabled, a crash report will be created locally and then you will be able to share it. + Přidat instanci + Zapnout hlášení o pádech aplikace + Pokud je zapnuto, místně se vytvoří hlášení o pádu a pak ho budete moci sdílet. Fedilab přestal fungovat :( - Pošlete mi mailem údaje o chybě. Pomůžete tak při opravě :)\n\nMůžete přidat dodatečný obsah. Děkuji! - Visibility + Pošlete mi mailem údaje o chybě. Pomůžete tak při opravě :) +\n +\nMůžete přidat dodatečný obsah. Děkuji! + Viditelnost Disable custom animated emojis - Report account + Nahlásit účet - %d voter - %d voters - %d voters - %d voters + %d hlasující + %d hlasující + %d hlasujících Jediná volba @@ -508,82 +508,89 @@ 3 dny 7 dní - Your poll can\'t have duplicated options! - Clear cache when leaving - The cache (media, cached messages, data from the built-in browser) will be automatically cleared when leaving the application. - Do you want to unfollow this account? - Show confirmation dialog before unfollowing - Replace Medium links - Replace medium.com links with an open source alternative front-end focused on privacy. - Default: scribe.rip - Use a push notifications system for getting notifications in real time. - Add notes - Notes for the account - Allow to compress large photos into smaller sized photos with very less or negligible loss in quality of the image. - Allow compressing videos while maintaining their quality. - Order by - Links - Change the color of links (URLs, mentions, tags, etc.) in messages - Reblogs header + Vaše anketa má duplicitní volby! + Při opuštění vymazat cache + Cache (média, cachované zprávy, data z vestavěného prohlížeče) se při opuštění aplikace automaticky vymaže. + Chcete přestat sledovat tento účet\? + Před ukončením sledování zobrazit potvrzovací dialog + Medium + Použít alternativní frontend pro Medium + Doména frontendu pro Medium + Používat systém push notifikací pro získávání upozornění v reálném čase. + Přidat poznámky + Poznámky k účtu + Umožnit kompresi velkých fotografií na menší velikost s velmi malou až zanedbatelnou ztrátou kvality. + Umožnit kompresi videa při udržení kvality. + Řadit podle + Odkazy + Změnit ve zprávách barvu odkazů (URL, zmínek, tagů apod.) + Hlavička reblogů Change the color of display name at the top of messages Change the color of the user name at the top of messages - Change the color of the header for reblogs - Posts - Background color of posts in timelines - Reset colors + Změnit barvu hlavičky pro reblogy + Příspěvky + Barva pozadí příspěvků v časových osách + Resetovat barvy Tap here to reset all your custom colors Reset - Icons - Color of bottom icons in timelines - Logo of the instance - Edit profile - Make an action - Translation - Text color - Change the text color in messages - Use a custom theme - Theming - The theme was exported - The theme has been successfully exported in CSV - Import a theme + Ikony + Barva dolních ikon v časových osách + Logo instance + Upravit profil + Provést akci + Překlad + Barva textu + Změnit barvu textu ve zprávách + Použít vlastní téma + Témata + Téma bylo exportováno + Téma bylo úspěšně exportováno do CSV + Importovat téma Tap here to import a theme from a previous export - Export the theme + Exportovat téma Tap here to export the current theme - An error occurred when selecting the theme file - User count - Status count - Instance count + Při výběru souboru s tématem došlo k chybě + Počet uživatelů + Počet statusů + Počet instancí End in %s - This instance is not available on https://instances.social - Display full link - Share link - Open with another app - Check redirect - This URL does not redirect - %1$s \n\nredirects to\n\n %2$s - Remove UTM parameters - The app will automatically remove UTM parameters from URLs before visiting a link. - %d people talking - Twitter accounts (via Nitter) - Twitter usernames space separated - Identity proofs - Verified identity - Verified by %1$s (%2$s) - Action disabled - Unfollow - Something went wrong, please check your download directory in settings. - Announcements - No announcements! - Add a reaction - Video cache in MB, zero means no cache. - Watermarks - Automatically add a watermark at the bottom of pictures. The text can be customized for each account. - No distributors found! - You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message. - Select a distributor + Tato instance není k dispozici na https://instances.social + Zobrazit úplný odkaz + Sdílet odkaz + Otevřít jinou aplikací + Zkontrolovat přesměrování + Tento URL není přesměrování + %1$s +\n +\npřesměrovává na +\n +\n %2$s + Odstranit parametry UTM + Aplikace bude před otevřením odkazu automaticky odstraňovat parametry UTM. + Hovoří %d lidí + Účty Twitteru (přes Nitter) + Uživatelská jména Twitteru oddělená mezerou + Ověření identity + Ověřená identita + Ověřil(a) %1$s (%2$s) + Akce vypnuta + Zrušit sledování + Došlo k nějaké chybě, zkontrolujte prosím nastavení adresáře pro stahování. + Oznámení + Žádná oznámení! + Přidat reakci + Video cache v MB, nula znamená žádnou cache. + Vodoznaky + Automaticky přidávat vodoznak do dolní části obrázků. Text lze pro každý účet samostatně nastavit. + Nenalezeni žádní distributoři! + Pro příjem push notifikací potřebujete distributora. +\nDalší podrobnosti najdete na %1$s. +\n +\nMůžete také ignorovat tuto zprávu tak, že push notifikace v nastavení vypnete. + Vybrat distributora Vymazat cache Víte, že to porušuje určitá pravidla - Typy oznámení k zobrazení + Typy upozornění k zobrazení Boostuje také: Jsem moderátor Naposledy aktivní @@ -592,13 +599,13 @@ Schváleno Jediný panel akcí Zpráva byla odeslána! - Typ hlasování: - Doba trvání hlasování: + Typ ankety: + Doba trvání ankety: Nejnovější Filtr Velikosti ikon Výchozí viditelnost zpráv: - Počet oznámení na jedno nahrání + Počet upozornění na jedno nahrání Doména frontendu Redditu Skrýt obsah < Je to spam @@ -609,10 +616,10 @@ „Mastodon není jediný web jako Twitter nebo Facebook, je to síť tisíců komunit provozovaných různými organizacemi a jednotlivci, kteří poskytují bezproblémové zážitky se sociálními médii.“ Oblíbené Aktualizace od lidí - Vymazat všechna oznámení - Označit všechna oznámení jako přečtená + Vymazat všechna upozornění + Označit všechna upozornění jako přečtená Zobrazit všechny kategorie - Opravdu chcete smazat všechna oznámení\? Nelze to vzít zpět. + Opravdu chcete smazat všechna upozornění\? Nelze to vzít zpět. Opravdu chcete smazat toto pole\? Je to něco jiného Která pravidla jsou porušována\? @@ -652,7 +659,7 @@ Můj účet Vymazat cache Poznámky k vydání - Vypnout oznámení + Vypnout upozornění Během tohoto časového úseku Základ tématu Původ nahlášeného účtu @@ -664,19 +671,19 @@ Nastavení byla úspěšně exportována Vlastní Nemám to rád(a) - Boostováno - Oblíbeno u + Boostoval(a) + Oblíbil(a) si Jen sledující Porušuje to pravidla serveru Doplňující komentáře Přeposlat na %1$s Výsledky hlasování - Report byl odeslán! + Hlášení bylo odesláno! Interakce Účet je objevitelný - Typ oznámení + Typ upozornění Témata od přispěvatelů - Zvuky oznámení + Zvuky upozornění Personál Moje aplikace Opravdu chcete odejít bez uložení obrázku\? @@ -709,7 +716,7 @@ Profil byl aktualizován! Název seznamu není platný! To není něco, co chcete vidět - Tento účet je z jiného serveru. Poslat mu také anonymizovanou kopii reportu\? + Tento účet je z jiného serveru. Poslat mu také anonymizovanou kopii hlášení\? Nemáte účet\? Ahoj! Zveme vás k připojení do Fediverse. Zmínky @@ -733,7 +740,7 @@ verze: %s \n %s uživatelů - %s statusů Odebrat status - Vybrat nejlepší shodu + Vyberte nejlepší shodu Zkontrolováno: %s Přidat status Nové hlášení @@ -752,9 +759,9 @@ Cachované zprávy Vybrat téma Zobrazit časové osy - Vybrat typy oznámení + Vybrat typy upozornění Sdílená zpráva byla upravena - Zprávy v cache o ostatní časové osy + Zprávy v cache pro ostatní časové osy Vynutit překlad do určitého jazyka. K resetu do výchozího nastavení vyberte první hodnotu Poslední IP Povolit @@ -832,4 +839,87 @@ Smazat klíčové slovo Přidat klíčové slovo Filtrováno: %1$s + Nastavit prodlevu před dalším načtením + Pokud je zapnuto, aplikace sbalí související upozornění + V upozorněních zobrazovat média + Budou se zobrazovat média v upozorněních na reblogy a oblíbení + Přiřazeno mně + Zrušit přiřazení + Ignorovat všechna hlášení z této domény. Nerelevantní pro pozastavení + Ignorovat všechna hlášení přicházející z této domény. Nerelevantní pro pozastavení + Obfuskovat název domény + Vyvýšené karty + Pokud je zapnuto, položky v časových osách budou mít stín a vyvýšení. + Přizpůsobit světlé téma + Nastavit vlastní barvy + Světlé – vlastní barvy + Umožňuje přizpůsobit tmavému barevnému tématu některé elementy ve zprávách. + Reblogy + Zvolte, zda má být základ tématu tmavý nebo světlý + Existují nějaké příspěvky dokládající toto hlášení\? + Oznámení · %1$s - %2$s + Pokud je zapnuto, všechny připnuté časové osy se budou zobrazovat v rozbalovací nabídce + Načíst upozornění + Vybrat logo + Týká se jen „veřejných“ odpovědí. Pokud je zapnuto, vaše odpovědi budou mít automaticky „neuvedenou“ viditelnost namísto „veřejné“ + Upozornění byla odstraněna z cache. + Vlastní varování + Hlášené statusy + Pozastavení účtu zrušeno + Účet pozastaven + Upravit seznam + Akce filtru + Poslal(a) hlášení + odeslal(a) hlášení + Načítat upozornění každých: + Tmavé – vlastní barvy + Agregovat upozornění + Deaktivace účtu zrušena + Zachovat upozornění + Blokace domény nezabrání vytváření položek účtů v databázi, ale retroaktivně a automaticky na tyto účty aplikuje určité moderační metody. + Přizpůsobit tmavé téma + Bude zobrazovat bublinové počitadlo pro nové zprávy u časových os + Skrýt filtrovaný obsah za varování zmiňující titulek filtru + Úplně skrýt filtrovaný obsah, jako kdyby neexistoval + Umožňuje nastavit vaše vlastní barvy pro témata. + Umožňuje přizpůsobit světlému barevnému tématu některé elementy ve zprávách. + Řadit seznamy + Odmítat hlášení + Částečně obfuskovat název domény v seznamu, pokud je zapnuto zveřejňování seznamu doménových omezení + Čas načítání upozornění + Pokud je zapnuto, aplikace bude mít jen jeden panel pro časové osy + %1$s upravil(a) %2$s + Účet deaktivován + Hlášení + Aktualizace + Domov a seznamy + Aplikaci se nepovedlo přidat účet do seznamu! + Registroval(a) se + Bez zájmu + Upozornit na aktualizace + Nová registrace (moderátoři) + Nové hlášení (moderátoři) + Odmítat hlášení + Komentář ohledně omezení této domény pro obecnou veřejnost, pokud je zapnuto zveřejňování seznamu doménových omezení. + Komentář ohledně omezení této domény pro interní použití moderátory. + Výběr režimu pro téma + Umožňuje omezit seznam jazyků ve výběru při vytváření zprávy. + Změnit logo aplikace na vašem zařízení + Přizpůsobuje tonalitu barevného schématu podle vaší osobní tapety. + Zprávy v cache pro domovskou časovou osu + Distributor pro push + Upravil(a) zprávu + Vyberte, kterou akci provést, pokud bude příspěvek vyhovovat filtru + Umožňuje vytvořit vaše vlastní téma + Odpovědi s neuvedenou viditelností + Zobrazit vzdálenou konverzaci + Konverzace začala na vaší instanci! + Aplikace nenašla vzdálenou zprávu. + Výběr z témat vytvořených přispěvateli + Zkuste to prosím později znovu. + Nastavte svůj max. počet znaků + Vždy zobrazovat tlačítko překladu + Připnout tag + Nesledovat tag + Odepnout tag \ No newline at end of file diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml index 23daf82d..3ef98dd5 100644 --- a/app/src/main/res/values-gd/strings.xml +++ b/app/src/main/res/values-gd/strings.xml @@ -21,7 +21,6 @@ Facal-faire Brathan Co-roinn - " " Iomraidhean Annsachdan Tha diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 9fa1310b..d23d3869 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -903,4 +903,17 @@ Segue a tonalidade do fondo de pantalla para axustar o esquema de cores. Decorado claro por defecto Decorado escuro por defecto + Personalizar Decorado Claro + Permite personalizar algúns elementos nas mensaxes para o decorado claro. + Permite personalizar algúns elementos da mensaxe no decorado escuro. + Establecer cores + Claro - Cores personais + Mostrar conversa remota + Inténtao outra vez máis tarde. + A app non atopa a mensaxe remota. + Se o activas, os elementos nas cronoloxías terán unha sombra e elevación. + Personalizar Decorado Escuro + A conversa comezou na túa instancia! + Escuro - Cores personais + Tarxetas elevadas \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 20424b2d..d61207ed 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -32,10 +32,10 @@ Szöveg és ikon méret Következő Előző - Megnyitás a következővel: + Megnyitás a következővel Jóváhagyás Média - Megosztás a következővel: + Megosztás a következővel Megosztva a Fedilabon keresztül Válaszok Felhasználónév @@ -207,8 +207,8 @@ Fiók zárolása Változások mentése Előnézeti képek méretre szabása - Kezdet: - Befejezés: + Kezdet + Befejezés Beépített böngésző használata Egyéni lapok cw automatikus kibontása diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4fa0fb93..3f8b11cd 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -208,7 +208,7 @@ 내장 브라우저 사용 커스텀 탭 CW를 자동으로 펼침 - LED 색 설정 + LED 색 설정: 파란색 청록색 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index c7d4aee9..8e9f9cbd 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -582,19 +582,19 @@ Stopp opptak Antall kontoer per innlasting Musikk - Dette feltet må fylles ut. + Dette feltet må fylles ut! Twitter YouTube Domene for Twitter-grenseflate Bruk en alternativ grenseflate for Instagram Annet Legg til status - Er oppe. + Er oppe! Oppetid: %,.2f %% Fortsett Tilpasset Kun følgere - Er nede. + Er nede! Sjekket: %s Forstum %1$s Blokker %1$s @@ -604,7 +604,7 @@ \n %s brukere - %s statuser Bruk en alternativ grenseflate for YouTube Domene for YouTube-grenseflate - Instansen ser ikke ut til å være gyldig. + Instansen ser ikke ut til å være gyldig! Framhevet av Favorittmerket av F.eks: Sensitivt innhold diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1838e8e8..2bc08d3a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -313,10 +313,12 @@ Baixar mídia Selecionar toque Ativar definição de momento - Tem certeza de que quer bloquear %s?\n\nSeus seguidores desta instância serão removidos, e você não verá nenhum conteúdo ou notificação desta instância. + Tem certeza que deseja bloquear %s\? +\n +\nVocê não verá nenhum conteúdo dessa instância em nenhuma linha do tempo pública ou em suas notificações. Seus seguidores dessa instância serão removidos. Bloquear instância - Instância bloqueada! - Carregando toot remoto! + Instância bloqueada + Obtendo status remoto Instância Peertube Usar Emoji One Informação @@ -635,4 +637,5 @@ Diga-nos o que está havendo com este post Adicionar status Enviando mensagem %d/%d + Nova atualização \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b4fa7896..e9e0e54a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -572,7 +572,7 @@ Отписаться Что-то пошло не так, пожалуйста, проверьте папку загрузок в настройках. Объявления - Объявлений пока нет. + Объявлений пока нет! Добавить реакцию Кэш видео в MB, ноль означает, нет кэша. Водяные знаки diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index ec8a2d6b..d616e1c4 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -532,7 +532,7 @@ Faghe un\'atzione Tradutzione Colore de su testu - Càmbia su colore de su testu in is ricuadros + Càmbia su colore de su testu in is messàgios Imprea unu tema personalizadu Temas As esportadu su tema @@ -901,4 +901,17 @@ Allìnia tonalmente cun s\'ischema de colores de s\'isfundu personale tuo. Ti permitit de cunfigurare is colores personalizados tuos pro is temas. Tema iscuru predefinidu + Personaliza su tema iscuru + Cunfigura colores personalizados + Craru - Colores personalizados + Iscuru - Colores personalizados + S\'arresonada est incumintzada in s\'istàntzia tua! + Personaliza su tema craru + Cartas elevadas + Cando est abilitadu is elementos in is lìnias de tempus ant a tènnere un\'umbra e un\'artària. + Permitit de personalizare unos cantos elementos in is messàgios pro su tema iscuru. + Ammustra s\'arresonada remota + Permitit de personalizare unos cantos elementos in is messàgios pro su tema craru. + Torra a proare prus a tardu. + S\'aplicatzione no at agatadu su messàgiu remotu. \ No newline at end of file diff --git a/app/src/main/res/values-szl/strings.xml b/app/src/main/res/values-szl/strings.xml index a2207c43..bcbdf91d 100644 --- a/app/src/main/res/values-szl/strings.xml +++ b/app/src/main/res/values-szl/strings.xml @@ -297,7 +297,7 @@ Srogość liter we tekście ani we upozorniyniu ô zawartości niy majōm znaczynio Ôdciep zamiast kryć Filtrowane tuty zniknōm doimyntnie, nawet jak filter bydzie wymazany - Jak słowo kluczowe abo fraza sōm ino alfanumeryczne, to to bydzie użyte, jak bydzie pasować cołke słowo + Jak słowo kluczowe abo fraza sōm ino alfanumeryczne, to bydzie użyte, jak bydzie pasować cołke słowo Cołke słowo Kōnteksty filtra Jedyn abo wiyncyj kōntekstōw, kaj filter winiyn być użyty diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 19d53d01..0c930bfa 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -534,7 +534,7 @@ Bir eylem yap Çeviri Metin rengi - İçeriklerdeki metin rengini değiştir + Mesajlardaki metin rengini değiştir Kişisel tema kullan Tema Tema dışa aktarıldı @@ -907,4 +907,23 @@ Öntanımlı koyu tema Tema için bir mod seçin Kişisel duvar kağıdınızın renk düzeniyle ton olarak uyum sağlayın. + Yükseltilmiş kartlar + Özel renkler ayarla + Koyu - Özel renkler + Etkinleştirildiğinde, zaman çizelgelerindeki ögelerin bir gölgesi ve bir yüksekliği olacaktır. + Koyu tema için mesajlardaki bazı ögeleri özelleştirmeye izin verir. + Açık Temayı Özelleştir + Açık tema için mesajlardaki bazı ögeleri özelleştirmeye izin verir. + Koyu Temayı Özelleştir + Açık - Özel renkler + Uzak görüşmeyi göster + Görüşme sizin sunucunuzda başladı! + Lütfen daha sonra tekrar deneyin. + Uygulama uzak mesajı bulamadı. + Etiketi sessize al + Etiketin sabitlemesini kaldır + Her zaman çevir düğmesini göster + Etiketin sesini aç + Etiketi sabitle + Etiketi takibi bırak \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 65ef9b63..795a5c6a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -527,7 +527,7 @@ 添加操作 翻译 文本颜色 - 更改点的文本颜色 + 更改消息中文本的颜色 使用自定义主题 主题 主题已导出 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 259a36f5..b38a0222 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1356,6 +1356,7 @@ SET_FILTER_REGEX_PUBLIC SET_NOTIF_VALIDATION SET_DISPLAY_BOOKMARK + SET_DISPLAY_TRANSLATE SET_NOTIF_VALIDATION_FAV SET_DISPLAY_COUNTER_FAV_BOOST SET_INNER_MARKER @@ -1423,6 +1424,7 @@ Poll type: Poll duration: Always display bookmark button + Always display translate button Display Bottom menu Top bar menu @@ -2048,4 +2050,13 @@ Set custom colors Light - Custom colors Dark - Custom colors + Display remote conversation + Please, try again later. + The conversation started on your instance! + The app didn\'t find the remote message. + Mute tag + Unmute tag + Pin tag + Unpin tag + Unfollow tag \ No newline at end of file diff --git a/app/src/main/res/xml/pref_categories.xml b/app/src/main/res/xml/pref_categories.xml index a7e6dfb4..3d2669c5 100644 --- a/app/src/main/res/xml/pref_categories.xml +++ b/app/src/main/res/xml/pref_categories.xml @@ -53,12 +53,14 @@ app:icon="@drawable/ic_theming" app:key="@string/pref_category_key_theming" /> + + +