diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 9caa6dea2a..39f06cf186 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -16,7 +16,7 @@ android { minSdk 23 targetSdk 33 versionCode 99 - versionName "2.0.3+fork.99" + versionName "2.1.4+fork.99" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW'] } diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java index 5abcf743d5..0fcc8a93c9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java @@ -143,7 +143,7 @@ public void handleURL(Uri uri, String accountID){ } public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){ - new GetSearchResults(q, null, true) + new GetSearchResults(q, null, true, null, 0, 0) .setCallback(new Callback<>(){ @Override public void onSuccess(SearchResults result){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java index 549c1640c7..b76dbe9deb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java +++ b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java @@ -176,6 +176,8 @@ private void notify(Context context, PushNotification pn, String accountID, org. List channels=Arrays.stream(PushNotification.Type.values()) .map(type->{ NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT); + channel.setLightColor(context.getColor(R.color.primary_700)); + channel.enableLights(true); channel.setGroup(accountID); return channel; }) @@ -205,6 +207,7 @@ private void notify(Context context, PushNotification pn, String accountID, org. .setShowWhen(true) .setCategory(Notification.CATEGORY_SOCIAL) .setAutoCancel(true) + .setLights(context.getColor(android.R.attr.colorAccent), 500, 1000) .setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent)); if (!GlobalUserPreferences.uniformNotificationIcon) { diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentialsPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentialsPreferences.java index 686b64e3f9..eecebf50d3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentialsPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentialsPreferences.java @@ -6,18 +6,19 @@ import org.joinmastodon.android.model.StatusPrivacy; public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest{ - public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){ + public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){ super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class); - setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage))); + setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage))); } private static class Request{ - public Boolean locked, discoverable; + public Boolean locked, discoverable, indexable; public RequestSource source; - public Request(Boolean locked, Boolean discoverable, RequestSource source){ + public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){ this.locked=locked; this.discoverable=discoverable; + this.indexable=indexable; this.source=source; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/filters/FilterRequest.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/filters/FilterRequest.java index ff61d536f7..9d5856a16b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/filters/FilterRequest.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/filters/FilterRequest.java @@ -6,6 +6,9 @@ import java.util.EnumSet; import java.util.List; +import androidx.annotation.Keep; + +@Keep class FilterRequest{ public String title; public EnumSet context; diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/search/GetSearchResults.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/search/GetSearchResults.java index 0407bb702a..f368edb21f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/search/GetSearchResults.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/search/GetSearchResults.java @@ -4,13 +4,19 @@ import org.joinmastodon.android.model.SearchResults; public class GetSearchResults extends MastodonAPIRequest{ - public GetSearchResults(String query, Type type, boolean resolve){ + public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){ super(HttpMethod.GET, "/search", SearchResults.class); addQueryParameter("q", query); if(type!=null) addQueryParameter("type", type.name().toLowerCase()); if(resolve) addQueryParameter("resolve", "true"); + if(maxID!=null) + addQueryParameter("max_id", maxID); + if(offset>0) + addQueryParameter("offset", String.valueOf(offset)); + if(count>0) + addQueryParameter("limit", String.valueOf(count)); } public GetSearchResults limit(int limit){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetTag.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetTag.java new file mode 100644 index 0000000000..06b90b1838 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetTag.java @@ -0,0 +1,10 @@ +package org.joinmastodon.android.api.requests.tags; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Hashtag; + +public class GetTag extends MastodonAPIRequest{ + public GetTag(String tag){ + super(HttpMethod.GET, "/tags/"+tag, Hashtag.class); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/SetTagFollowed.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/SetTagFollowed.java new file mode 100644 index 0000000000..4ecf6e7398 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/SetTagFollowed.java @@ -0,0 +1,11 @@ +package org.joinmastodon.android.api.requests.tags; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Hashtag; + +public class SetTagFollowed extends MastodonAPIRequest{ + public SetTagFollowed(String tag, boolean followed){ + super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class); + setRequestBody(new Object()); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index c94c87c1a2..c4c7fe09ed 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -219,7 +219,7 @@ public void savePreferencesLater(){ public void savePreferencesIfPending(){ if(preferencesNeedSaving){ - new UpdateAccountCredentialsPreferences(preferences, null, null) + new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable) .setCallback(new Callback<>(){ @Override public void onSuccess(Account result){ @@ -303,6 +303,10 @@ public void filterStatusContainingObjects(List objects, Function getInstance() { return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain)); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index 75c1e2d9b9..56b411a6ef 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -303,8 +303,7 @@ private void maybeUpdateCustomEmojis(Set domains, String activeDomain){ } } - - private void updateSessionLocalInfo(AccountSession session){ + /*package*/ void updateSessionLocalInfo(AccountSession session){ new GetOwnAccount() .setCallback(new Callback<>(){ @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index ac8850f78a..82b619bfff 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -1317,7 +1317,8 @@ private void openFilePicker(boolean photoPicker){ boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable(); if(usePhotoPicker){ intent=new Intent(MediaStore.ACTION_PICK_IMAGES); - intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()); + if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1) + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()); }else{ intent=new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FeaturedHashtagsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FeaturedHashtagsListFragment.java index c9234ea4cd..2e506e2cde 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FeaturedHashtagsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FeaturedHashtagsListFragment.java @@ -45,8 +45,8 @@ protected void addAccountToKnown(Hashtag s){ } @Override - public void onItemClick(String hashtag){ - UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null)); + public void onItemClick(String id){ + UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java index 8a0fb7a55b..6007911428 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FollowedHashtagsFragment.java @@ -121,7 +121,7 @@ public void onBind(Hashtag item) { @Override public void onClick() { - UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following); + UiUtils.openHashtagTimeline(getActivity(), accountID, item.name); } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java index 57cb1114cb..a805494c8a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java @@ -1,46 +1,64 @@ package org.joinmastodon.android.fragments; import android.app.Activity; +import android.content.res.TypedArray; import android.net.Uri; import android.os.Bundle; +import android.text.SpannableStringBuilder; import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; import android.widget.Toast; -import org.joinmastodon.android.E; import org.joinmastodon.android.R; -import org.joinmastodon.android.api.requests.tags.GetHashtag; -import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed; +import org.joinmastodon.android.api.MastodonErrorResponse; +import org.joinmastodon.android.api.requests.tags.GetTag; +import org.joinmastodon.android.api.requests.tags.SetTagFollowed; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; -import org.joinmastodon.android.events.HashtagUpdatedEvent; import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.TimelineDefinition; +import org.joinmastodon.android.ui.text.SpacerSpan; import org.joinmastodon.android.ui.utils.UiUtils; -import org.joinmastodon.android.utils.StatusFilterPredicate; +import org.joinmastodon.android.ui.views.ProgressBarButton; +import org.parceler.Parcels; import java.util.List; -import java.util.stream.Collectors; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; -public class HashtagTimelineFragment extends PinnableStatusListFragment { - private String hashtag; +public class HashtagTimelineFragment extends PinnableStatusListFragment{ + private Hashtag hashtag; + private String hashtagName; + private TextView headerTitle, headerSubtitle; + private ProgressBarButton followButton; + private ProgressBar followProgress; + private MenuItem followMenuItem, pinMenuItem; + private boolean followRequestRunning; + private boolean toolbarContentVisible; + private List any; private List all; private List none; private boolean following; private boolean localOnly; - private MenuItem followButton; + private Menu optionsMenu; + private MenuInflater optionsMenuInflater; @Override protected boolean wantsComposeButton() { @@ -50,73 +68,19 @@ protected boolean wantsComposeButton() { @Override public void onAttach(Activity activity){ super.onAttach(activity); - updateTitle(getArguments().getString("hashtag")); following=getArguments().getBoolean("following", false); localOnly=getArguments().getBoolean("localOnly", false); any=getArguments().getStringArrayList("any"); all=getArguments().getStringArrayList("all"); none=getArguments().getStringArrayList("none"); - setHasOptionsMenu(true); - } - - private void updateTitle(String hashtagName) { - hashtag = hashtagName; - setTitle('#'+hashtag); - } - - private void updateFollowingState(boolean newFollowing) { - this.following = newFollowing; - followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag)); - followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular); - E.post(new HashtagUpdatedEvent(hashtag, following)); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.hashtag_timeline, menu); - super.onCreateOptionsMenu(menu, inflater); - followButton = menu.findItem(R.id.follow_hashtag); - updateFollowingState(following); - - new GetHashtag(hashtag).setCallback(new Callback<>() { - @Override - public void onSuccess(Hashtag hashtag) { - if (getActivity() == null) return; - updateTitle(hashtag.name); - updateFollowingState(hashtag.following); - } - - @Override - public void onError(ErrorResponse error) { - error.showToast(getActivity()); - } - }).exec(accountID); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (super.onOptionsItemSelected(item)) return true; - if (item.getItemId() == R.id.follow_hashtag) { - updateFollowingState(!following); - getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK); - new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() { - @Override - public void onSuccess(Hashtag i) { - if (getActivity() == null) return; - if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show(); - updateFollowingState(i.following); - } - - @Override - public void onError(ErrorResponse error) { - error.showToast(getActivity()); - updateFollowingState(!following); - } - }).exec(accountID); - return true; + if(getArguments().containsKey("hashtag")){ + hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag")); + hashtagName=hashtag.name; + }else{ + hashtagName=getArguments().getString("hashtagName"); } - return false; + setTitle('#'+hashtagName); + setHasOptionsMenu(true); } @Override @@ -126,12 +90,10 @@ protected TimelineDefinition makeTimelineDefinition() { @Override protected void doLoadData(int offset, int count){ - currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility) + currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility) .setCallback(new SimpleCallback<>(this){ @Override public void onSuccess(List result){ - if (getActivity() == null) return; - result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList()); onDataLoaded(result, !result.isEmpty()); } }) @@ -146,15 +108,40 @@ protected void onShown(){ } @Override - public boolean onFabLongClick(View v) { - return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '); + public void loadData(){ + reloadTag(); + super.loadData(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + fab=view.findViewById(R.id.fab); + fab.setOnClickListener(this::onFabClick); + + if(getParentFragment() instanceof HomeTabFragment) return; + + list.addOnScrollListener(new RecyclerView.OnScrollListener(){ + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ + View topChild=recyclerView.getChildAt(0); + int firstChildPos=recyclerView.getChildAdapterPosition(topChild); + float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight()); + toolbarTitleView.setAlpha(newAlpha); + boolean newToolbarVisibility=newAlpha>0.5f; + if(newToolbarVisibility!=toolbarContentVisible){ + toolbarContentVisible=newToolbarVisibility; + createOptionsMenu(); + } + } + }); } @Override public void onFabClick(View v){ Bundle args=new Bundle(); args.putString("account", accountID); - args.putString("prefilledText", '#'+hashtag+' '); + args.putString("prefilledText", '#'+hashtagName+' '); Nav.go(getActivity(), ComposeFragment.class, args); } @@ -170,6 +157,203 @@ protected FilterContext getFilterContext() { @Override public Uri getWebUri(Uri.Builder base) { - return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build(); + return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build(); + } + + @Override + protected RecyclerView.Adapter getAdapter(){ + View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false); + headerTitle=header.findViewById(R.id.title); + headerSubtitle=header.findViewById(R.id.subtitle); + followButton=header.findViewById(R.id.profile_action_btn); + followProgress=header.findViewById(R.id.action_progress); + + headerTitle.setText("#"+hashtagName); + followButton.setVisibility(View.GONE); + followButton.setOnClickListener(v->{ + if(hashtag==null) + return; + setFollowed(!hashtag.following); + }); + followButton.setOnLongClickListener(v->{ + if(hashtag==null) return false; + UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> { + new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){ + @Override + public void onSuccess(Hashtag hashtag) { + Toast.makeText( + getActivity(), + getString(R.string.sk_followed_as, session.self.getShortUsername()), + Toast.LENGTH_SHORT + ).show(); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getActivity()); + } + }).exec(session.getID()); + }, null); + return true; + }); + updateHeader(); + + MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter(); + if(!(getParentFragment() instanceof HomeTabFragment)){ + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header)); + } + mergeAdapter.addAdapter(super.getAdapter()); + return mergeAdapter; + } + + @Override + protected int getMainAdapterOffset(){ + return 1; + } + + private void createOptionsMenu(){ + optionsMenu.clear(); + optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu); + followMenuItem=optionsMenu.findItem(R.id.follow_hashtag); + pinMenuItem=optionsMenu.findItem(R.id.pin); + followMenuItem.setVisible(toolbarContentVisible); + followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName)); + followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular); + pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS); + if(toolbarContentVisible){ + UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu); + }else{ + UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin); + } + } + + @Override + public void updatePinButton(MenuItem pin){ + super.updatePinButton(pin); + UiUtils.insetPopupMenuIcon(getContext(), pin); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + inflater.inflate(R.menu.hashtag_timeline, menu); + super.onCreateOptionsMenu(menu, inflater); + optionsMenu=menu; + optionsMenuInflater=inflater; + createOptionsMenu(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + if (super.onOptionsItemSelected(item)) return true; + if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) { + setFollowed(!hashtag.following); + } + return true; + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f); + createOptionsMenu(); + } + + private void updateHeader(){ + if(hashtag==null) + return; + + if(hashtag.history!=null && !hashtag.history.isEmpty()){ + int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum(); + int todayPosts=hashtag.history.get(0).uses; + int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum(); + int hSpace=V.dp(8); + SpannableStringBuilder ssb=new SpannableStringBuilder(); + ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts)); + ssb.append(" ", new SpacerSpan(hSpace, 0), 0); + ssb.append('·'); + ssb.append(" ", new SpacerSpan(hSpace, 0), 0); + ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts)); + ssb.append(" ", new SpacerSpan(hSpace, 0), 0); + ssb.append('·'); + ssb.append(" ", new SpacerSpan(hSpace, 0), 0); + ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts)); + headerSubtitle.setText(ssb); + } + + int styleRes; + followButton.setVisibility(View.VISIBLE); + if(hashtag.following){ + followButton.setText(R.string.button_following); + styleRes=R.style.Widget_Mastodon_M3_Button_Tonal; + }else{ + followButton.setText(R.string.button_follow); + styleRes=R.style.Widget_Mastodon_M3_Button_Filled; + } + TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background}); + followButton.setBackground(ta.getDrawable(0)); + ta.recycle(); + ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor}); + followButton.setTextColor(ta.getColorStateList(0)); + followProgress.setIndeterminateTintList(ta.getColorStateList(0)); + ta.recycle(); + + followButton.setTextVisible(true); + followProgress.setVisibility(View.GONE); + if(followMenuItem!=null){ + followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName)); + followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular); + UiUtils.insetPopupMenuIcon(getContext(), followMenuItem); + } + } + + private void reloadTag(){ + new GetTag(hashtagName) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Hashtag result){ + hashtag=result; + updateHeader(); + } + + @Override + public void onError(ErrorResponse error){ + + } + }) + .exec(accountID); + } + + private void setFollowed(boolean followed){ + if(followRequestRunning) + return; + getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK); + followButton.setTextVisible(false); + followProgress.setVisibility(View.VISIBLE); + followRequestRunning=true; + new SetTagFollowed(hashtagName, followed) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Hashtag result){ + if(getActivity()==null) + return; + hashtag=result; + updateHeader(); + followRequestRunning=false; + } + + @Override + public void onError(ErrorResponse error){ + if(getActivity()==null) + return; + if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){ + hashtag.following=true; + }else{ + error.showToast(getActivity()); + } + updateHeader(); + followRequestRunning=false; + } + }) + .exec(accountID); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index b5e2d06cdb..0ce9645972 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -184,6 +184,7 @@ public boolean onPreDraw(){ }); } } + tabBar.selectTab(currentTab); return content; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java index 4d407429f5..72155601ff 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -524,9 +524,7 @@ public boolean onOptionsItemSelected(MenuItem item){ if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); Nav.go(getActivity(), ListTimelineFragment.class, args); } else if ((hashtag = hashtagsItems.get(id)) != null) { - args.putString("hashtag", hashtag.name); - args.putBoolean("following", hashtag.following); - Nav.go(getActivity(), HashtagTimelineFragment.class, args); + UiUtils.openHashtagTimeline(getContext(), accountID, hashtag); } return true; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 951bd14c68..c726f408f2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -304,6 +304,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary)); tabbar.setTabTextSize(V.dp(14)); + tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){ + case 0 -> R.string.profile_featured; + case 1 -> R.string.profile_timeline; + case 2 -> R.string.profile_about; + default -> throw new IllegalStateException(); + })); tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){ @Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ @@ -317,6 +323,19 @@ public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ if (position == 4) tab.view.setVisibility(View.GONE); } }); + tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){ + @Override + public void onTabSelected(TabLayout.Tab tab){} + + @Override + public void onTabUnselected(TabLayout.Tab tab){} + + @Override + public void onTabReselected(TabLayout.Tab tab){ + if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt) + stt.scrollToTop(); + } + }); cover.setOutlineProvider(new ViewOutlineProvider(){ @Override @@ -1304,9 +1323,7 @@ private class ProfilePagerAdapter extends RecyclerView.Adapter @NonNull @Override public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - FrameLayout view=tabViews[viewType]; - if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view); - view.setVisibility(View.VISIBLE); + FrameLayout view=new FrameLayout(parent.getContext()); view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return new SimpleViewHolder(view); } @@ -1314,8 +1331,13 @@ public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewTy @Override public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ Fragment fragment=getFragmentForPage(position); + FrameLayout fragmentView=tabViews[position]; + fragmentView.setVisibility(View.VISIBLE); + if(fragmentView.getParent() instanceof ViewGroup parent) + parent.removeView(fragmentView); + ((FrameLayout)holder.itemView).addView(fragmentView); if(!fragment.isAdded()){ - getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit(); + getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit(); holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ @Override public boolean onPreDraw(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java index 31a981a7a5..bd513adc14 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java @@ -24,6 +24,7 @@ import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem; @@ -105,6 +106,12 @@ protected List buildDisplayItems(Status s){ text.textSelectable=true; else if(item instanceof FooterStatusDisplayItem footer) footer.hideCounts=true; + else if(item instanceof SpoilerStatusDisplayItem spoiler){ + for(StatusDisplayItem subItem:spoiler.contentItems){ + if(subItem instanceof TextStatusDisplayItem text) + text.textSelectable=true; + } + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/ComposeAccountSearchFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/ComposeAccountSearchFragment.java index 4d01cc38ac..965c2d49c0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/ComposeAccountSearchFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/ComposeAccountSearchFragment.java @@ -48,7 +48,7 @@ public void onViewCreated(View view, Bundle savedInstanceState){ @Override protected void doLoadData(int offset, int count){ refreshing=true; - currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false) + currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0) .setCallback(new SimpleCallback<>(this){ @Override public void onSuccess(SearchResults result){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index 3d578d0fd8..290a349f0b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -289,15 +289,19 @@ private class DiscoverPagerAdapter extends RecyclerView.Adapter UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following); + case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag); case STATUS -> { Status status=res.status.getContentStatus(); Bundle args=new Bundle(); @@ -113,7 +112,7 @@ public void onItemClick(String id){ } @Override - protected void doLoadData(int offset, int count){ + protected void doLoadData(int _offset, int count){ GetSearchResults.Type type; if(currentFilter.size()==1){ type=switch(currentFilter.iterator().next()){ @@ -128,7 +127,21 @@ protected void doLoadData(int offset, int count){ dataLoaded(); return; } - currentRequest=new GetSearchResults(currentQuery, type, true) + String maxID=null; + // TODO server-side bug + /*int offset=0; + if(_offset>0){ + if(type==GetSearchResults.Type.STATUSES){ + if(!preloadedData.isEmpty()) + maxID=preloadedData.get(preloadedData.size()-1).status.id; + else if(!data.isEmpty()) + maxID=data.get(data.size()-1).status.id; + }else{ + offset=_offset; + } + }*/ + int offset=_offset; + currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count) .setCallback(new Callback<>(){ @Override public void onSuccess(SearchResults result){ @@ -142,12 +155,15 @@ public void onSuccess(SearchResults result){ results.add(new SearchResult(tag)); } if(result.statuses!=null){ - for(Status status:result.statuses) - results.add(new SearchResult(status)); + Set alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet()); + for(Status status:result.statuses){ + if(!alreadyLoadedStatuses.contains(status.id)) + results.add(new SearchResult(status)); + } } prevDisplayItems=new ArrayList<>(displayItems); unfilteredResults=results; - onDataLoaded(filterSearchResults(results), false); + onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty()); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchQueryFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchQueryFragment.java index 8db5c48c29..b304c6e82e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchQueryFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchQueryFragment.java @@ -122,7 +122,7 @@ protected void doLoadData(int offset, int count){ recentsHeader.setVisible(!data.isEmpty()); }); }else{ - currentRequest=new GetSearchResults(currentQuery, null, false) + currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0) .limit(2) .setCallback(new SimpleCallback<>(this){ @Override @@ -378,7 +378,7 @@ public void onAnimationEnd(Animator animation){ private void openHashtag(SearchResult res){ wrapSuicideDialog(()->{ - UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following); + UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res); }); } @@ -424,6 +424,8 @@ private void wrapSuicideDialog(Runnable r){ } private void onSearchViewEnter(){ + if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty()) + return; wrapSuicideDialog(()->deliverResult(currentQuery, null)); } @@ -436,7 +438,7 @@ private void onGoToHashtagClick(){ String q=searchViewHelper.getQuery(); if(q.startsWith("#")) q=q.substring(1); - UiUtils.openHashtagTimeline(getActivity(), accountID, q, null); + UiUtils.openHashtagTimeline(getActivity(), accountID, q); }); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java index f9c34a65bd..c8dd851704 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java @@ -105,7 +105,7 @@ public void onBind(Hashtag item){ @Override public void onClick(){ - UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following); + UiUtils.openHashtagTimeline(getActivity(), accountID, item); } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java index 8455100c6d..d457cca1ff 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java @@ -271,7 +271,7 @@ public void head(Node node, int depth){ @Override public void tail(Node node, int depth){ if(node instanceof Element){ - ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java index 24eb7afefd..1bfb5c5db8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java @@ -33,7 +33,7 @@ public void onCreate(Bundle savedInstanceState){ setTitle(getString(R.string.about_app, getString(R.string.sk_app_name))); AccountSession s=AccountSessionManager.get(accountID); onDataLoaded(List.of( - new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)), + new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))), new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))), new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")), new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true), diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java index 844b44a180..6adcaf4dfb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java @@ -55,6 +55,8 @@ public void onCreate(Bundle savedInstanceState){ onDataLoaded(List.of( new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick), new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick), + new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick), + new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick), new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick), new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick), new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true), @@ -69,7 +71,9 @@ public void onCreate(Bundle savedInstanceState){ data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true)); } - account.reloadPreferences(null); + AccountSession session=AccountSessionManager.get(accountID); + session.reloadPreferences(null); + session.updateAccountInfo(); E.register(this); } @@ -133,6 +137,10 @@ private void onDisplayClick(){ Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs()); } + private void onPrivacyClick(){ + Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs()); + } + private void onFiltersClick(){ Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs()); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsPrivacyFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsPrivacyFragment.java new file mode 100644 index 0000000000..743dd97ab8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsPrivacyFragment.java @@ -0,0 +1,41 @@ +package org.joinmastodon.android.fragments.settings; + +import android.os.Bundle; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.viewmodel.CheckableListItem; + +import java.util.List; + +public class SettingsPrivacyFragment extends BaseSettingsFragment{ + private CheckableListItem discoverableItem, indexableItem; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setTitle(R.string.settings_privacy); + Account self=AccountSessionManager.get(accountID).self; + onDataLoaded(List.of( + discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)), + indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem)) + )); + if(self.source.indexable==null) + indexableItem.isEnabled=false; + } + + @Override + protected void doLoadData(int offset, int count){} + + @Override + public void onPause(){ + super.onPause(); + Account self=AccountSessionManager.get(accountID).self; + if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){ + self.discoverable=discoverableItem.checked; + self.source.indexable=indexableItem.checked; + AccountSessionManager.get(accountID).savePreferencesLater(); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsServerFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsServerFragment.java index 0b067ca378..3fd882fafc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsServerFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsServerFragment.java @@ -153,18 +153,21 @@ private class ServerPagerAdapter extends RecyclerView.Adapter{ @NonNull @Override public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - FrameLayout view=tabViews[viewType]; - ((ViewGroup)view.getParent()).removeView(view); - view.setVisibility(View.VISIBLE); + FrameLayout view=new FrameLayout(parent.getContext()); view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return new SimpleViewHolder(view); } @Override public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ + FrameLayout view=tabViews[position]; + if(view.getParent() instanceof ViewGroup parent) + parent.removeView(view); + view.setVisibility(View.VISIBLE); + ((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); Fragment fragment=getFragmentForPage(position); if(!fragment.isAdded()){ - getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit(); + getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit(); holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ @Override public boolean onPreDraw(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java index 8b153295ee..d64453d584 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java @@ -139,6 +139,7 @@ public class Account extends BaseModel implements Searchable{ * When a timed mute will expire, if applicable. */ public Instant muteExpiresAt; + public boolean noindex; public List roles; @@ -238,6 +239,7 @@ public String toString(){ ", source="+source+ ", suspended="+suspended+ ", muteExpiresAt="+muteExpiresAt+ + ", noindex="+noindex+ '}'; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Hashtag.java b/mastodon/src/main/java/org/joinmastodon/android/model/Hashtag.java index 4d1554faf3..0566ea85fc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Hashtag.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Hashtag.java @@ -23,6 +23,7 @@ public String toString(){ ", following="+following+ ", history="+history+ ", statusesCount="+statusesCount+ + ", following="+following+ '}'; } @@ -30,4 +31,19 @@ public String toString(){ public String getID(){ return name; } + + @Override + public boolean equals(Object o){ + if(this==o) return true; + if(o==null || getClass()!=o.getClass()) return false; + + Hashtag hashtag=(Hashtag) o; + + return name.equals(hashtag.name); + } + + @Override + public int hashCode(){ + return name.hashCode(); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Mention.java b/mastodon/src/main/java/org/joinmastodon/android/model/Mention.java index 3b20bb7141..80188c771d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Mention.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Mention.java @@ -20,4 +20,22 @@ public String toString(){ ", url='"+url+'\''+ '}'; } + + @Override + public boolean equals(Object o){ + if(this==o) return true; + if(o==null || getClass()!=o.getClass()) return false; + + Mention mention=(Mention) o; + + if(!id.equals(mention.id)) return false; + return url.equals(mention.url); + } + + @Override + public int hashCode(){ + int result=id.hashCode(); + result=31*result+url.hashCode(); + return result; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Source.java b/mastodon/src/main/java/org/joinmastodon/android/model/Source.java index 6307d0ccd0..49095611b8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Source.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Source.java @@ -37,6 +37,8 @@ public class Source extends BaseModel{ * The number of pending follow requests. */ public int followRequestCount; + public Boolean indexable; + public boolean hideCollections; @Override public void postprocess() throws ObjectValidationException{ @@ -54,6 +56,8 @@ public String toString(){ ", sensitive="+sensitive+ ", language='"+language+'\''+ ", followRequestCount="+followRequestCount+ + ", indexable="+indexable+ + ", hideCollections="+hideCollections+ '}'; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index 6c7b9cd54b..3ac230424e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -5,6 +5,17 @@ import android.text.TextUtils; +import org.joinmastodon.android.api.ObjectValidationException; +import org.joinmastodon.android.api.RequiredField; +import org.joinmastodon.android.events.StatusCountersUpdatedEvent; +import org.joinmastodon.android.ui.text.HtmlParser; +import org.parceler.Parcel; + +import java.time.Instant; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + import androidx.annotation.NonNull; import com.github.bottomSoftwareFoundation.bottom.Bottom; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java index 96c7b47bff..ddab996629 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java @@ -219,7 +219,7 @@ public Bundle populateArguments(Bundle args) { args.putString("listID", listId); args.putBoolean("listIsExclusive", listIsExclusive); } else if (type == TimelineType.HASHTAG) { - args.putString("hashtag", hashtagName); + args.putString("hashtagName", hashtagName); args.putBoolean("localOnly", hashtagLocalOnly); args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny)); args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll)); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 097aae9f0e..005bd255d8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -15,6 +15,7 @@ import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; +import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Status; @@ -53,7 +54,8 @@ public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragmen public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) { super(parentID, parentFragment); SpannableStringBuilder ssb=new SpannableStringBuilder(text); - HtmlParser.parseCustomEmoji(ssb, emojis); + if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames) + HtmlParser.parseCustomEmoji(ssb, emojis); this.text=ssb; emojiHelper.setText(ssb); this.icon=icon; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index fb630fdcbc..7554424087 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -180,10 +180,8 @@ public static ArrayList buildItems(BaseStatusListFragment .ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem( parentID, fragment, hashtag.name, List.of(), R.drawable.ic_fluent_number_symbol_20sp_filled, null, - i -> { - args.putString("hashtag", hashtag.name); - Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args); - }, status + i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag), + status ))); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java index ce2caf2211..3082753596 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java @@ -878,6 +878,8 @@ public void reset(){ @Override public void onVideoSizeChanged(MediaPlayer mp, int width, int height){ + if(width<=0 || height<=0) + return; FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams(); params.width=width; params.height=height; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java index 58a4dfe2e3..ac9f5fbaa9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/ZoomPanView.java @@ -119,6 +119,9 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto int width=right-left; int height=bottom-top; + if(width==0 || height==0) + return; + float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight()); minScale=scale; maxScale=Math.max(3f, height/(float)child.getHeight()); @@ -306,8 +309,6 @@ public void setValue(ZoomPanView object, float value){ }, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA)); } }else{ - if(animatingTransition) - Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace()); child.setScaleX(matrixValues[Matrix.MSCALE_X]); child.setScaleY(matrixValues[Matrix.MSCALE_Y]); child.setTranslationX(matrixValues[Matrix.MTRANS_X]); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java index fb6d013d38..b0a3dc8fd1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java @@ -100,9 +100,10 @@ public SpanInfo(Object span, int start, Element element, boolean more){ } } - Map idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id)); + Map idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id)); // Hashtags in remote posts have remote URLs, these have local URLs so they don't match. // Map tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name)); + Map tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity())); final SpannableStringBuilder ssb=new SpannableStringBuilder(); Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){ @@ -115,6 +116,7 @@ public void head(@NonNull Node node, int depth){ }else if(node instanceof Element el){ switch(el.nodeName()){ case "a" -> { + Object linkObject=null; String href=el.attr("href"); LinkSpan.Type linkType; String text=el.text(); @@ -122,6 +124,7 @@ public void head(@NonNull Node node, int depth){ if(text.startsWith("#")){ linkType=LinkSpan.Type.HASHTAG; href=text.substring(1); + linkObject=tagsByTag.get(text.substring(1).toLowerCase()); }else{ linkType=LinkSpan.Type.URL; } @@ -136,7 +139,7 @@ public void head(@NonNull Node node, int depth){ }else{ linkType=LinkSpan.Type.URL; } - openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el)); + openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, linkObject, text), ssb.length(), el)); } case "br" -> ssb.append('\n'); case "span" -> { @@ -271,7 +274,7 @@ public static CharSequence parseLinks(String text){ String url=matcher.group(3); if(TextUtils.isEmpty(matcher.group(4))) url="http://"+url; - ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0); + ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, null, null), matcher.start(3), matcher.end(3), 0); }while(matcher.find()); // Find more URLs return ssb; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java index 795a6ddfa1..fe08613680 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java @@ -5,6 +5,7 @@ import android.text.style.CharacterStyle; import android.view.View; +import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.ui.utils.UiUtils; public class LinkSpan extends CharacterStyle { @@ -14,17 +15,15 @@ public class LinkSpan extends CharacterStyle { private String link; private Type type; private String accountID; + private Object linkObject; private String text; - public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){ - this(link, listener, type, accountID, null); - } - - public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){ + public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, Object linkObject, String text){ this.listener=listener; this.link=link; this.type=type; this.accountID=accountID; + this.linkObject=linkObject; this.text=text; } @@ -42,7 +41,12 @@ public void onClick(Context context){ switch(getType()){ case URL -> UiUtils.openURL(context, accountID, link); case MENTION -> UiUtils.openProfileByID(context, accountID, link); - case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null); + case HASHTAG -> { + if(linkObject instanceof Hashtag ht) + UiUtils.openHashtagTimeline(context, accountID, ht); + else + UiUtils.openHashtagTimeline(context, accountID, text); + } case CUSTOM -> listener.onLinkClick(this); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 673df5b0bf..a5aeb881dc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -104,6 +104,7 @@ import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Notification; +import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.SearchResults; @@ -445,12 +446,18 @@ public static void openProfileByID(Context context, String selfID, String id) { Nav.go((Activity) context, ProfileFragment.class, args); } - public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) { - Bundle args = new Bundle(); + public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("hashtag", Parcels.wrap(hashtag)); + Nav.go((Activity)context, HashtagTimelineFragment.class, args); + } + + public static void openHashtagTimeline(Context context, String accountID, String hashtag){ + Bundle args=new Bundle(); args.putString("account", accountID); - args.putString("hashtag", hashtag); - if (following != null) args.putBoolean("following", following); - Nav.go((Activity) context, HashtagTimelineFragment.class, args); + args.putString("hashtagName", hashtag); + Nav.go((Activity)context, HashtagTimelineFragment.class, args); } public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) { @@ -1157,7 +1164,7 @@ public static Optional> return Optional.empty(); } - return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() { + return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() { @Override public void onSuccess(SearchResults results) { Optional result = extractResult.apply(results); @@ -1254,7 +1261,7 @@ public static Optional> lookupAccountHandle(Co } public static MastodonAPIRequest lookupAccountHandle(Context context, String accountID, Pair> queryHandle, BiConsumer, Bundle> go) { String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse("")); - return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true) + return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0) .setCallback(new Callback<>() { @Override public void onSuccess(SearchResults results) { @@ -1317,7 +1324,7 @@ public void onError(ErrorResponse error){ }) .execNoAuth(uri.getHost())); } else if (looksLikeFediverseUrl(url)) { - return Optional.of(new GetSearchResults(url, null, true) + return Optional.of(new GetSearchResults(url, null, true, null, 0, 0) .setCallback(new Callback<>() { @Override public void onSuccess(SearchResults results) { diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java index 1f8dacef1b..95cc2ccfc7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java @@ -15,15 +15,12 @@ import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.search.GetSearchResults; import org.joinmastodon.android.api.session.AccountSessionManager; -import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.viewmodel.AccountViewModel; import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.OutlineProviders; -import org.joinmastodon.android.ui.text.HtmlParser; -import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.FilterChipView; @@ -96,6 +93,24 @@ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull R outRect.right=V.dp(8); } }); + // Set empty adapter to prevent NPEs + list.setAdapter(new RecyclerView.Adapter<>(){ + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + throw new UnsupportedOperationException(); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){ + + } + + @Override + public int getItemCount(){ + return 0; + } + }); contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); emptyButton=new FilterChipView(activity); @@ -222,11 +237,13 @@ public Mode getMode(){ } private void doSearchUsers(){ - currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false) + currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0) .setCallback(new Callback<>(){ @Override public void onSuccess(SearchResults result){ currentRequest=null; + if(mode!=Mode.USERS) + return; List oldList=users; users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()); if(isLoading){ @@ -256,7 +273,7 @@ public void onError(ErrorResponse error){ } private void doSearchHashtags(){ - currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false) + currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false, null, 0, 0) .setCallback(new Callback<>(){ @Override public void onSuccess(SearchResults result){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java index 52aa0a88f9..05c61b8d30 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java @@ -75,14 +75,14 @@ public void setView(View view, Bundle savedInstanceState){ Instance instance=fragment.instance; if (!instance.isAkkoma()) { - if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0) + if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0) maxPollOptions=instance.configuration.polls.maxOptions; - if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0) + if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0) maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption; } else { - if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0) + if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptions>0) maxPollOptions=instance.pollLimits.maxOptions; - if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0) + if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0) maxPollOptionLength=instance.pollLimits.maxOptionChars; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/MediaGridLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/MediaGridLayout.java index 4f9cbbed66..eacdb120cc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/MediaGridLayout.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/MediaGridLayout.java @@ -15,7 +15,7 @@ public class MediaGridLayout extends ViewGroup{ private static final int GAP=2; // dp private PhotoLayoutHelper.TiledLayoutResult tiledLayout; - private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10]; + private int[] columnStarts, columnEnds, rowStarts, rowEnds; public MediaGridLayout(Context context){ this(context, null); @@ -41,6 +41,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH)); } + if(rowStarts==null || rowStarts.length + + diff --git a/mastodon/src/main/res/drawable/ic_thumbs_up_down_24px.xml b/mastodon/src/main/res/drawable/ic_thumbs_up_down_24px.xml new file mode 100644 index 0000000000..1605696083 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_thumbs_up_down_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/layout/header_hashtag_timeline.xml b/mastodon/src/main/res/layout/header_hashtag_timeline.xml new file mode 100644 index 0000000000..acd626c6a0 --- /dev/null +++ b/mastodon/src/main/res/layout/header_hashtag_timeline.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/menu/hashtag_timeline.xml b/mastodon/src/main/res/menu/hashtag_timeline.xml index efc7cc2e21..633cbaaea6 100644 --- a/mastodon/src/main/res/menu/hashtag_timeline.xml +++ b/mastodon/src/main/res/menu/hashtag_timeline.xml @@ -1,13 +1,12 @@ - + \ No newline at end of file diff --git a/mastodon/src/main/res/values-ar-rSA/strings.xml b/mastodon/src/main/res/values-ar-rSA/strings.xml index 6102493a8a..3e977c4f60 100644 --- a/mastodon/src/main/res/values-ar-rSA/strings.xml +++ b/mastodon/src/main/res/values-ar-rSA/strings.xml @@ -8,7 +8,7 @@ حسنًا جَارٍ الإعدَادُ لِلمُصادَقَة… يُنهي المصادقة… - %s إعادة نشر + قام %s بإعادة نشر ردًا على %s الإشعارات %s بَدَأ بِمُتابَعَتِك @@ -242,7 +242,7 @@ تخطى متابعُون جُدُد المفضلة - المشاركات + المعاد نشرها الإشارات استطلاع رأي اختر حسابًا @@ -277,7 +277,7 @@ مزيد من الخيارات منشور جديد ردّ - شارك + إعادة النشر فضّل شارك وسائط بدون وصف @@ -292,8 +292,8 @@ أنت تتابع الآن %s طَلَبَ %s مُتابَعتك افتح في المتصفح - اخف مشاركات %s - أظهر مشاركات %s + أخفِ المعاد نشرها مِن %s + أظهر ما أعاد %s نشرَه لماذا تريد الانضمام؟ هذا سوف يساعدنا في مراجعة تطبيقك. امسح @@ -346,10 +346,10 @@ %,d تفضيل - %,d إعادة نشر + لم يُعد نشره إعادة نشر واحدة أعيد نشره مرّتان - أعيد نشره %,d مرة + أعيد نشره %,d مرات أعيد نشره %,d مرات أعيد نشره %,d مرات @@ -698,4 +698,18 @@ مُنذُ %dد مُنذُ %dسا مُنذُ %d أيام + + تُرجِم مِن %s + + مُترجَم مِن %1$s باستخدام %2$s + إظهار الأصل + فشِلَت الترجَمة. قد لم يتمكّن مدير الخادم من تفعيل الترجمات على هذا الخادم أو أنّ هذا الخادم يُشغِّل نسخة قديمة من ماستدون حيث الترجمات غير مدعومة بعد. + + لا مُشارِك + مشارِك واحد + مشاركَيْنِ + مشاركين + مُشارِكًا + مُشارك + diff --git a/mastodon/src/main/res/values-be-rBY/strings.xml b/mastodon/src/main/res/values-be-rBY/strings.xml index 47deb50b44..16378b7a76 100644 --- a/mastodon/src/main/res/values-be-rBY/strings.xml +++ b/mastodon/src/main/res/values-be-rBY/strings.xml @@ -527,4 +527,6 @@ %d хв таму %d г таму %d дз таму + + diff --git a/mastodon/src/main/res/values-bn-rBD/strings.xml b/mastodon/src/main/res/values-bn-rBD/strings.xml index 506df12093..02cd04b2ea 100644 --- a/mastodon/src/main/res/values-bn-rBD/strings.xml +++ b/mastodon/src/main/res/values-bn-rBD/strings.xml @@ -199,4 +199,6 @@ + + diff --git a/mastodon/src/main/res/values-bs-rBA/strings.xml b/mastodon/src/main/res/values-bs-rBA/strings.xml index 6bfbad87f3..9cfe8ec7da 100644 --- a/mastodon/src/main/res/values-bs-rBA/strings.xml +++ b/mastodon/src/main/res/values-bs-rBA/strings.xml @@ -182,4 +182,6 @@ + + diff --git a/mastodon/src/main/res/values-ca-rES/strings.xml b/mastodon/src/main/res/values-ca-rES/strings.xml index c770f010e9..9affcf4fda 100644 --- a/mastodon/src/main/res/values-ca-rES/strings.xml +++ b/mastodon/src/main/res/values-ca-rES/strings.xml @@ -293,4 +293,6 @@ + + diff --git a/mastodon/src/main/res/values-cs-rCZ/strings.xml b/mastodon/src/main/res/values-cs-rCZ/strings.xml index f9f5623b16..3efaf9a944 100644 --- a/mastodon/src/main/res/values-cs-rCZ/strings.xml +++ b/mastodon/src/main/res/values-cs-rCZ/strings.xml @@ -639,4 +639,6 @@ Před %dm Před %dh Před %dd + + diff --git a/mastodon/src/main/res/values-da-rDK/strings.xml b/mastodon/src/main/res/values-da-rDK/strings.xml index 1133776f58..500bc0a803 100644 --- a/mastodon/src/main/res/values-da-rDK/strings.xml +++ b/mastodon/src/main/res/values-da-rDK/strings.xml @@ -503,4 +503,6 @@ Åbn URL i Mastodon Indlæg med “%s” + + diff --git a/mastodon/src/main/res/values-de-rDE/strings.xml b/mastodon/src/main/res/values-de-rDE/strings.xml index c0a48dd378..78bde6558b 100644 --- a/mastodon/src/main/res/values-de-rDE/strings.xml +++ b/mastodon/src/main/res/values-de-rDE/strings.xml @@ -582,4 +582,6 @@ vor %d Minuten vor %d Stunden vor %d Tagen + + diff --git a/mastodon/src/main/res/values-el-rGR/strings.xml b/mastodon/src/main/res/values-el-rGR/strings.xml index 8b8722accf..588549c5cd 100644 --- a/mastodon/src/main/res/values-el-rGR/strings.xml +++ b/mastodon/src/main/res/values-el-rGR/strings.xml @@ -387,6 +387,7 @@ Καλώς ήρθες στο Mastodon Το Mastodon είναι ένα αποκεντρωμένο κοινωνικό δίκτυο που σημαίνει ότι καμία εταιρεία δεν το ελέγχει. Αποτελείται από πολλούς ανεξάρτητους διακομιστές, όλοι συνδεδεμένοι μαζί. Τι είναι οι διακομιστές; + Κάθε λογαριασμός Mastodon φιλοξενείται σε ένα διακομιστή - ο καθένας με τις δικές του αξίες, κανόνες & διαχειριστές. Ανεξάρτητα από το ποιον μπορεί να επιλέξεις, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με άτομα από οποιονδήποτε διακομιστή. Άνοιγμα συνδέσμου… Αυτός ο σύνδεσμος δεν υποστηρίζεται στην εφαρμογή Αποσύνδεση από όλους τους λογαριασμούς @@ -581,4 +582,21 @@ %dλ πριν %dώ πριν %dημ πριν + + Μετάφραση από %s + + Μεταφράστηκε από %1$s χρησιμοποιώντας %2$s + Εμφάνιση αρχικού + Η μετάφραση απέτυχε. Ίσως ο διαχειριστής δεν έχει ενεργοποιήσει μεταφράσεις σε αυτόν τον διακομιστή ή αυτός ο διακομιστής εκτελεί μια παλαιότερη έκδοση του Mastodon όπου οι μεταφράσεις δεν υποστηρίζονται ακόμα. + Ιδιωτικότητα και προσιτότητα + Παροχή προφίλ και δημοσιεύσεων σε αλγορίθμους ανακάλυψης + Συμπερίληψη δημόσιων αναρτήσεων στα αποτελέσματα αναζήτησης + + %,d συμμετέχων + %,d συμμετέχοντες + + + %,d ανάρτηση σήμερα + %,d αναρτήσεις σήμερα + diff --git a/mastodon/src/main/res/values-es-rES/strings.xml b/mastodon/src/main/res/values-es-rES/strings.xml index 1cdee41422..07771e198e 100644 --- a/mastodon/src/main/res/values-es-rES/strings.xml +++ b/mastodon/src/main/res/values-es-rES/strings.xml @@ -573,4 +573,6 @@ hace %dm hace %dh hace %dd + + diff --git a/mastodon/src/main/res/values-eu-rES/strings.xml b/mastodon/src/main/res/values-eu-rES/strings.xml index 62fca5584d..0455397f01 100644 --- a/mastodon/src/main/res/values-eu-rES/strings.xml +++ b/mastodon/src/main/res/values-eu-rES/strings.xml @@ -441,4 +441,6 @@ Garbitu dena Ireki URLa Mastodonen + + diff --git a/mastodon/src/main/res/values-fa-rIR/strings.xml b/mastodon/src/main/res/values-fa-rIR/strings.xml index 2040a79ae1..de9647539c 100644 --- a/mastodon/src/main/res/values-fa-rIR/strings.xml +++ b/mastodon/src/main/res/values-fa-rIR/strings.xml @@ -387,6 +387,7 @@ به ماستودون خوش آمدید ماستودون یک شبکه اجتماعی غیر متمرکز است،به این معنی که هیچ شرکتی آن را کنترل نمی کند. این از بسیاری از کارسازهای مستقل تشکیل شده است که همه به هم متصل هستند. کارساز شما کجاست؟ + هر حساب ماستودون بر روی یک سرور میزبانی می شود — هر کدام با مقادیر، قوانین، & مدیران خاص خود. مهم نیست کدام یک را انتخاب می کنید، می توانید افراد را در هر کارسازی دنبال کنید و با آنها تعامل داشته باشید. باز کردن پیوند… این پیوند در کاره پشتیبانی نمی شود از همه حساب‌ها خارج شوید @@ -581,4 +582,21 @@ %d؜دقيقه پيش %dساعت پيش %dروز پیش + + ترجمه از %s + + ترجمه از %1$s با %2$s + نمایش اصلی + ترجمه ناموفق بود. شاید مدیر ترجمه‌ها را در این کارساز فعال نکرده باشد یا این کارساز نسخه قدیمی ماستودون را اجرا می کند که در آن ترجمه‌ها هنوز پشتیبانی نمی شوند. + محرمانگی و دسترسی + مشخص کردن مشخصات و فرسته‌ها در الگوریتم‌های اکتشاف + قرار دادن فرسته‌های عمومی در نتایج جستجو + + %,d شرکت کننده + %,d شرکت‌کننده + + + %,d فرسته امروز + %,d فرسته امروز + diff --git a/mastodon/src/main/res/values-fi-rFI/strings.xml b/mastodon/src/main/res/values-fi-rFI/strings.xml index 7f56a49ded..80be06d17f 100644 --- a/mastodon/src/main/res/values-fi-rFI/strings.xml +++ b/mastodon/src/main/res/values-fi-rFI/strings.xml @@ -2,13 +2,167 @@ Kirjaudu sisään Seuraava + Haetaan palvelimen tietoja… Virhe + %s ei näytä olevan Mastodonin palvelin. + OK + Valmistellaan todennusta… + Viimeistellään todennusta… + %s tehosti + Vastauksessa %s + Ilmoitukset + %s seurasi sinua + %s lähetti sinulle seurauspyynnön + %s tykkäsi julkaisustasi + %s tehosti viestiäsi + Katso tulokset äänestyksestä johon osallistuit Jaa Asetukset + Julkaise + Hylkää luonnos? + Hylkää + Kumoa + + seuraaja + seuraajat + + + seurataan + seurataan + + Viestit + Viestit ja vastaukset + Media + Tietoja + Seuraa + Seurataan Muokkaa profiilia + Jaa profiili + Mykistä %s + Poista mykistys tililtä %s + Estä %s + Poista käyttäjän %s esto + Raportoi %s + Estä %s + Poista käyttäjän %s esto + + %,d julkaisu + %,d julkaisua + + Liittynyt Valmis + Ladataan… + Nimi + Sisältö Tallennetaan… + Julkaisu tililtä %s + Vaihtoehto %d + + %d minuutti + %d minuuttia + + + %d tunti + %d tuntia + + + %d päivä + %d päivää + + + %d sekunti jäljellä + %d sekunttia jäljellä + + + %d minuutti jäljellä + %d minuuttia jäljellä + + + %d tunti jäljellä + %d tuntia jäljellä + + + %d päivä jäljellä + %d päivää jäljellä + + + %d ääni + %d ääntä + + Suljettu + Mykistä tili + Vahvista käyttäjän %s mykistys + Mykistä + Poista tilin mykistys + Vahvista, että haluat poistaa mykistyksen tililtä %s + Poista mykistys + Estä tili + Estä verkkotunnus + Vahvista käyttäjän %s esto + Estä + Poista tilin esto + Poista verkkotunnuksen esto + Vahvista, että haluat poistaa tilin %s eston + Poista esto + Estetty + Äänestä + Poista + Poista julkaisu + Haluatko varmasti poistaa tämän julkaisun? + Poistetaan… + Äänen toisto + Toista + Tauko + Kirjaudu ulos + Lisää tili + Haku + Aihetunnisteet + Uutiset + Sinulle + Kaikki + Maininnat + + %d henkilö puhuu + %d henkilöä puhuu + + Raportoi %s + Mikä on väärin tässä julkaisussa? + Mikä on vialla käyttäjässä %s? + Valitse se, mikä sopii parhaiten + En pidä siitä + Tätä ei halua nähdä + Se on roskapostia + Haitalliset linkit, väärennetyt sitoutumiset tai toistuvat vastaukset + Se rikkoo palvelimen sääntöjä + Tiedät, että se rikkoo tiettyjä sääntöjä + Jotain muuta + Ongelma ei sovi muihin kategorioihin + Mitä sääntöjä rikotaan? + Valitse kaikki sopivat + Onko julkaisuja, jotka tukevat tätä raporttia? + Valitse kaikki sopivat + Olisiko jotain muuta, mitä meidän pitäisi tietää? + Lisäkommentit + Lähetetään raporttia… + Kiitos raportista, tutkimme asiaa. + Sillä välin kun tarkistamme tätä, voit ryhtyä toimenpiteisiin käyttäjää @%s vastaan: + Lopeta käyttäjän %s seuraaminen + Lopeta seuraaminen + Et näe hänen viestejään. Hän voi silti seurata sinua ja nähdä viestisi. Hän ei tiedä, että on mykistetty. + Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet. + Etkö halua nähdä tätä? + Tässä on vaihtoehtosi hallita näkemääsi Mastodonissa: Takaisin + Palvelimen nimi tai URL-osoite + Palvelimen säännöt + Jatkamalla sitoudut noudattamaan seuraavia sääntöjä, jotka %s moderaattorit ovat asettaneet ja valvoneet. + Luo tili + Nimi + Käyttäjätunnus + Sähköposti + Salasana + Vahvista salasana + Sisällytä suuraakkoset, erikoismerkit, ja numerot, jotta voit lisätä salasanan voimaa. Akateeminen Aktivismi Kaikki @@ -22,39 +176,424 @@ Musiikki Alueellinen Teknologia + Tarkista sähköpostisi + Napauta lähettämäämme linkkiä vahvistaaksesi tunnuksen %s. Odotamme täällä. + Etkö saanut linkkiä? + Lähetä uudelleen + Avaa sähköpostiohjelma + Vahvistusviesti lähetetty + Kirjoita tai liitä mitä mietit + Sisältövaroitus Tallenna + Lisää selitys + Julkinen + Vain seuraajat + Vain mainitsemani tilit + Viimeisimmät + Ohita + Uudet seuraajat + Suosikit + Tehostukset + Maininnat + Kyselyt + Valitse tili + Kirjaudu ensin Mastodoniin + + Et voi lisätä enempää kuin %d medialiitteen + Et voi lisätä enempää kuin %d medialiitettä + + Tiedosto %s on tyyppiä jota ei tueta + Tiedosto %1$s ylittää %2$s MB kokorajan + Ulkoasu + Käytä laitteen ulkoasua Vaalea Tumma + Toiminnot + Toista animoidut käyttäjäkuvat ja emojit + Käytä sovelluksen sisäistä selainta + Ilmoitukset + Osallistu Mastodoniin + Käyttöehdot + Tietosuojakäytäntö + Tyhjennä median välimuisti + Mastodon Android v%1$s (%2$d) + Median välimuisti tyhjennetty + Kirjaudu ulos %s? + Kirjoittaja merkitsi tämän median arkaluontoiseksi. + Avaa profiili %s + Lisää asetuksia + Uusi julkaisu Vastaa + Tehosta + Suosikki Jaa + Kuva ilma kuvausta + Lisää mediatiedosto + Lisää kysely Emoji + Kotiaikajana + Oma profiili + Median katselin Follow %s + Käyttäjän %s seuraaminen lopetettu + Seuraat nyt käyttäjää %s + Käyttäjän %s seuraamista pyydetty Avaa selaimessa + Piilota käyttäjän @%s tehostukset + Näytä tehostukset käyttäjältä @%s + Miksi haluat liittyä? + Tämä auttaa meitä arvioimaan hakemustasi. Tyhjennä + Otsikon kuva Profiilikuva + Järjestä uudelleen Lataa + Käyttöoikeus vaaditaan + Sovellus tarvitsee pääsyn tallennustilaan, jotta voit tallentaa tämän tiedoston. Avaa asetukset + Virhe tallennettaessa tiedostoa Tiedosto tallennettu Ladataan… + Tätä toimintoa käsittelevää sovellusta ei ole + Paikallinen + Nämä julkaisut ovat saamassa vetoa eri puolilla Mastodonia. + Näistä uutisista puhutaan Mastodonissa. + Nämä ovat viestit kaikilta palvelimesi (%s) käyttäjiltä. + Muiden seuraamiesi perusteella saattaisit pitää näistä tileistä. + Uusia julkaisuja + Lataa puuttuvat julkaisut Seuraa takaisin + Pyydetty + Seuraa sinua + Hyväksyy seuraajat käsin + + %d seuraaja + %d seuraajaa + + + %d seurattu + %d seurattua + + + %,d suosikki + %,d suosikkia + + + %,d tehostus + %,d tehostusta + + %1$s sovelluksella %2$s + nyt + Muokkaushistoria + Muokattiin viimeksi %s + juuri nyt + + %d sekunti sitten + %d sekuntia sitten + + + %d minuutti sitten + %d minuuttia sitten + + muokattu %s + Alkuperäinen viesti + Tekstiä muokattu + Sisältövaroitus + Sisältövaroitus muokattu + Sisältövaroitus poistettu + Kysely lisätty + Kyselyä muokattu + Kysely poistettu + Mediatiedosto lisätty + Mediatiedosto poistettu + Mediatiedostoja järjestetty + Merkitty arkaluontoiseksi + Merkitty ei arkaluontoiseksi + Julkaisu muokattu + Muokkaa + Hylätäänkö muutokset? + Lataus epäonnistui + %d tavua + %.2f KB + %.2f MB + %.2f GB + Käsitellään… + Lataa (%s) + Asenna + Yksityisyytesi + Vaikka Mastodon-sovellus ei kerää mitään tietoja, palvelimella, jonka olet rekisteröitynyt, voi olla eri käytäntö.\n\nJos olet eri mieltä käytännöstä %s, voit palata ja valita eri palvelin. + Hyväksyn + Luettelo on tyhjä + Tämä palvelin ei hyväksy uusia rekisteröintejä. + Kopioitu leikepöydälle + Kirjanmerkki + Poista kirjanmerkki + Kirjanmerkit + Omat suosikit + Tervetuloa takaisin + Kirjaudu sisään palvelimella, jossa olet luonut tilisi. + Palvelimen URL-osoite + Palvelin valitaan kielesi perusteella, jos jatkat ilman valintaa. + Millä tahansa kielellä + Välitön rekisteröityminen + Manuaalinen hyväksyntä + Mikä tahansa rekisteröintinopeus + Eurooppa + Pohjois-Amerikka + Etelä-Amerikka + Afrikka + Aasia + Oseania + Ei hyväksy uusia jäseniä + Erityiset Kiinnostukset + Salasanat eivät täsmää + Valitse minulle + Lisää rivi + Profiilin asetukset + Voit myös täyttää tämän myöhemmin Profiili-välilehdellä. + Voit lisätä enintään neljä profiilikenttää joissa on mitä haluat. Sijainti, linkit, pronominit — taivas on rajana. + Suosittua Mastodonissa + Seuraa kaikkia + Eri mieltä + TL;DR: Emme kerää tai käsittele mitään. + Eri mieltä tästä %s + Kuvaus + Seurataan käyttäjiä… + %1$s ei salli ilmoittautumisia osoitteesta %2$s. Kokeile toista osoitetta tai <a>valitse toinen palvelin</a>. + Näytä joka tapauksessa + Piilota uudelleen + Valitse yksi tai useampi + Tallenna muutokset + Suositellut + Aikajana + Näytä kaikki + Tilit + Vahvistettu linkki + Näytä + Piilota + Liity palvelimelle %s + Valitse toinen palvelin + tai + Lue lisää + Tervetuloa Mastodoniin + Mastodon on hajautettu sosiaalinen verkosto, joka tarkoittaa sitä, ettei sitä hallitse mikään yksittäinen yritys. Se koostuu monista itsenäisesti ylläpidetyistä palvelimista, jotka on liitetty yhteen. + Mitä palvelimet ovat? + Jokainen Mastodon tili isännöi palvelimella - kullakin on omat arvot, säännöt, & ylläpitäjät. Riippumatta siitä, minkä valitset, voit seurata ja olla vuorovaikutuksessa ihmisten kanssa millä tahansa palvelimella. + Avataan linkki… + Tämä linkki ei ole tuettu sovelluksessa + Kirjaudu ulos kaikista tileistä + Kirjaudu ulos kaikista tileistä? + Yritä uudelleen + Viestin lähettäminen epäonnistui + %s kuva + %s video + %s ääni + %s tiedosto + Kuva + Video + Ääni + GIF + Tiedosto + %d%% ladattu + Lisää kyselyyn vaihtoehto + Kyselyn kesto + Tyyli + Valitse yksi + Monivalinta + Poista kyselyn vaihtoehto + Kyselyn tyyli + Selitys + Ohje + Mikä on selitys? + Kuvaselitys auttaa ihmisiä, joilla on näkövamma, hidas yhteys, tai jotka tarvitsevat lisäkontekstia. \n\nVoit parantaa saavutettavuutta ja kaikkien mahdollisuutta ymmärtää kirjoittamalla selkeän, lyhyen ja objektiivisen selityksen. \n\n
  • Mainitse tärkeät elementit
  • \n
  • Anna tiivistelmä kuvissa olevista teksteistä
  • \n
  • Käytä normaalia lauserakennetta
  • \n
  • Vältä turhaa toistoa
  • \n
  • Keskity monimutkaisissa kuvioissa (kuten kartoissa ja taulukoissa) trendeihin ja tärkeimpiin tietoihin
+ Muokkaa julkaisua + Ei todennettua linkkiä + Selaa emojeita + Löydä etsimäsi henkilöt + Näille hakusanoille ei löytynyt mitään + Kieli + Oletus + Järjestelmä + Tunnista kieli + Ei voi tunnistaa kieltä + Tunnistettu + Media piilotettu + Julkaisu piilotettu + Raportoi julkaisu + Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne? + Välitä kohteeseen %s + Raportoitu + Jos et enää halua nähdä tämän käyttäjän julkaisuja kotiaikajanallasi, lopeta seuraaminen. + Käyttäjä %s mykistetty + Olet jo estänyt tämän käyttäjän, sinun ei tarvitse tehdä muuta sillä aikaa kun tarkastamme raporttisi. + Olet jo estänyt tämän käyttäjän, joten sinun ei tarvitse tehdä mitään muuta.\n\nKiitos, että autat pitämään Mastodonin turvallisena paikkana kaikille! + Estetty %s + Merkitse kaikki luetuksi + Näyttö + Suodattimet + Yleiskatsaus, säännöt, valvojat + Tietoa: %s + Julkaisun oletuskieli + Lisää muistutus kuvaselityksestä + Kysy ennen kuin käyttäjän seuraaminen lopetetaan + Kysy ennen tehostusta + Kysy ennen julkaisujen poistamista + Keskeytä kaikki + Pois Päältä + Kuka tahansa + Seuraajasi + Seuraamasi henkilöt + Ei kukaan + Ota vastaan ilmoituksia käyttäjältä + Maininnat ja vastaukset + Keskeytä kaikki ilmoitukset + + %d viikko + %d viikkoa + + %1$s klo %2$s + tänään + eilen + huomenna + Päättyy %s + Ilmoitukset jatkuvat %s. + Jatka nyt + Siirry ilmoitusasetuksiin + Tietoja + Säännöt + Ylläpitäjä + Viestin ylläpitäjä + Ota ilmoitukset käyttöön laitteesi asetuksista nähdäksesi päivityksiä mistä tahansa. + Vielä enemmän asetuksia + Näytä sisältövaroitukset + Piilota arkaluontoiseksi merkitty media + Näytä reaktiolaskurit + Mukautetut emojit näyttönimissä + + %d sekunnin kuluttua + %d sekunnin kuluttua + + + %d minuutin kuluttua + %d minuutin kuluttua + + + %d tunnin kuluttua + %d tunnin kuluttua + + + %d tunti sitten + %d tuntia sitten + + Mediasta puuttuu selitysteksti + + %s kuvastasi puuttuu selitysteksti. Julkaistaanko silti? + %s kuvastasi puuttuu selitysteksti. Julkaistaanko silti? + + + %s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti? + %s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti? + + Yksi + Kaksi + Kolme + Neljä + Julkaise + Lopeta käyttäjän %s seuraaminen? + Aktiivinen + Ei käytössä + Lisää suodatin + Muokkaa suodatinta + Kesto + Mykistetyt sanat + Mykistä alkaen + Näytä sisältövaroituksella + Näytä vielä viestejä, jotka täsmäävät tähän suodattimeen, mutta sisällönvaroituksen takana + Poista suodatin + Ikuisesti + Päättyy %s + + %d mykistetty sana tai lause + %d mykistettyä sanaa tai lauseita + + %1$s ja %2$s + %1$s, %2$s ja %3$s + %1$s, %2$s, ja %3$d lisää + Koti & listat + Ilmoitukset + Julkiset aikajanat + Langat & vastaukset + Profiilit + Otsikko + Poistetaanko suodatin “%s”? + Tämä suodatin poistetaan tililtäsi kaikissa laitteissa. + Lisää mykistetty sana + Muokkaa mykistettyä sanaa + Lisää + Sana tai lause + Sanat ovat tapauskohtaisia ja vastaavat vain kokonaisia sanoja.\n\nJos suodattaa avainsana “Apple”, se piilottaa viestit sisältävät “omena” tai “aPPLe” mutta ei “ananas\". + Poista sana “%s”? + Valitse + Valitse kaikki + Suodattimen kesto + Mukautettu + + Poista %d sana? + Poista %d sanaa? + + + %d valittu + %d valittu + + Tätä ei voi jättää tyhjäksi + Jo luettelossa + Sovelluksen päivitys valmis + Versio %s + Ladataan (%d%%) + Sopii suodattimeen ”%s” + Etsi Mastodonista + Tyhjennä kaikki + Avaa URL-osoite Mastodonissa + Julkaisut joissa on \"%s\" + Siirry tiliin %s + Julkaisut joissa on \"%s\" + Henkilöt jossa on \"%s\" + %ds sitten + %dm sitten + %dh sitten + %dd sitten + + Käännetty kielestä %s + + Käännetty kielestä %1$s käyttäen %2$s + Näytä alkuperäinen + Käännös epäonnistui. Ehkä järjestelmänvalvoja ei ole ottanut käyttöön käännöksiä tällä palvelimella tai tällä palvelimella on käynnissä vanhempi versio Mastodonista, jossa käännöksiä ei vielä tueta. + + %d osallistuja + %d osallistujaa + + + %,d viesti tänään + %,d viestiä tänään +
diff --git a/mastodon/src/main/res/values-fil-rPH/strings.xml b/mastodon/src/main/res/values-fil-rPH/strings.xml index 3ef668afdc..9f5f2ef8cb 100644 --- a/mastodon/src/main/res/values-fil-rPH/strings.xml +++ b/mastodon/src/main/res/values-fil-rPH/strings.xml @@ -211,12 +211,12 @@ %,d tagasunod - %, d Sumusunod - %, d Sumusunod + %,d Sumusunod + %,d Sumusunod - %, d Paborito - %, d paborito + %,d Paborito + %,d paborito %1$s sa pamamagitan ng %2$s ngayon @@ -284,4 +284,6 @@ + + diff --git a/mastodon/src/main/res/values-fr-rFR/strings.xml b/mastodon/src/main/res/values-fr-rFR/strings.xml index f62e302515..5b813c26cc 100644 --- a/mastodon/src/main/res/values-fr-rFR/strings.xml +++ b/mastodon/src/main/res/values-fr-rFR/strings.xml @@ -582,4 +582,18 @@ il y a %dm Il y a %dh Il y a %dj + + Traduire depuis %s + + Traduit depuis %1$s via %2$s + Afficher l’original + La traduction a échoué. Peut-être que l’administrateur n’a pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge. + + %,d participant + %,d participants + + + %,d message aujourd’hui + %,d messages aujourd’hui + diff --git a/mastodon/src/main/res/values-ga-rIE/strings.xml b/mastodon/src/main/res/values-ga-rIE/strings.xml index 0994605c78..2d5f561272 100644 --- a/mastodon/src/main/res/values-ga-rIE/strings.xml +++ b/mastodon/src/main/res/values-ga-rIE/strings.xml @@ -20,4 +20,6 @@ + + diff --git a/mastodon/src/main/res/values-gd-rGB/strings.xml b/mastodon/src/main/res/values-gd-rGB/strings.xml index 5f60a474de..2af78ea1c8 100644 --- a/mastodon/src/main/res/values-gd-rGB/strings.xml +++ b/mastodon/src/main/res/values-gd-rGB/strings.xml @@ -640,4 +640,6 @@ %dm air ais %du air ais %dl air ais + + diff --git a/mastodon/src/main/res/values-gl-rES/strings.xml b/mastodon/src/main/res/values-gl-rES/strings.xml index 77cf9dd359..550afd2608 100644 --- a/mastodon/src/main/res/values-gl-rES/strings.xml +++ b/mastodon/src/main/res/values-gl-rES/strings.xml @@ -4,12 +4,14 @@ Seguinte Obtendo info do servidor… Erro + %s non semella ser un servidor Mastodon. OK Preparándose para a autenticación… Rematando coa autenticación… %s promoveu Como resposta a %s Notificacións + %s comezou a seguirte Compartir Axustes Publicar @@ -347,4 +349,6 @@ + + diff --git a/mastodon/src/main/res/values-hi-rIN/strings.xml b/mastodon/src/main/res/values-hi-rIN/strings.xml index db87da5eaa..c667f61207 100644 --- a/mastodon/src/main/res/values-hi-rIN/strings.xml +++ b/mastodon/src/main/res/values-hi-rIN/strings.xml @@ -47,4 +47,6 @@ + + diff --git a/mastodon/src/main/res/values-hr-rHR/strings.xml b/mastodon/src/main/res/values-hr-rHR/strings.xml index 22fa3a1986..1af7dc9a5e 100644 --- a/mastodon/src/main/res/values-hr-rHR/strings.xml +++ b/mastodon/src/main/res/values-hr-rHR/strings.xml @@ -227,4 +227,6 @@ + + diff --git a/mastodon/src/main/res/values-hu-rHU/strings.xml b/mastodon/src/main/res/values-hu-rHU/strings.xml index 65827b1b04..469deb2a34 100644 --- a/mastodon/src/main/res/values-hu-rHU/strings.xml +++ b/mastodon/src/main/res/values-hu-rHU/strings.xml @@ -372,4 +372,6 @@ + + diff --git a/mastodon/src/main/res/values-hy-rAM/strings.xml b/mastodon/src/main/res/values-hy-rAM/strings.xml index d6e0531b57..48b1644f7e 100644 --- a/mastodon/src/main/res/values-hy-rAM/strings.xml +++ b/mastodon/src/main/res/values-hy-rAM/strings.xml @@ -382,4 +382,6 @@ + + diff --git a/mastodon/src/main/res/values-ig-rNG/strings.xml b/mastodon/src/main/res/values-ig-rNG/strings.xml index 0994605c78..2d5f561272 100644 --- a/mastodon/src/main/res/values-ig-rNG/strings.xml +++ b/mastodon/src/main/res/values-ig-rNG/strings.xml @@ -20,4 +20,6 @@ + + diff --git a/mastodon/src/main/res/values-in-rID/strings.xml b/mastodon/src/main/res/values-in-rID/strings.xml index 738b809e6b..80680fa33e 100644 --- a/mastodon/src/main/res/values-in-rID/strings.xml +++ b/mastodon/src/main/res/values-in-rID/strings.xml @@ -368,6 +368,7 @@ Selamat datang di Mastodon Mastodon adalah jejaring sosial terdesentralisasi, tidak ada satu pun perusahaan yang mengontrol. Semua dijalankan oleh server independen, terkoneksi bersama. Apa itu server? + Semua akun Mastodon berada pada sebuah server — dengan nilai, aturan, & admin masing-masing. Mana pun yang Anda pilih, Anda dapat mengikuti dan berinteraksi dengan server mana pun. Membuka tautan… Tautan ini tidak didukung dalam aplikasi Keluar dari semua akun @@ -552,4 +553,6 @@ %dm yang lalu %dj yang lalu %dh yang lalu + + diff --git a/mastodon/src/main/res/values-is-rIS/strings.xml b/mastodon/src/main/res/values-is-rIS/strings.xml index 25f3bf8f8a..0f8f224741 100644 --- a/mastodon/src/main/res/values-is-rIS/strings.xml +++ b/mastodon/src/main/res/values-is-rIS/strings.xml @@ -582,4 +582,21 @@ fyrir %dm síðan fyrir %dh síðan fyrir %dd síðan + + Þýða úr %s + + Þýtt úr %1$s með %2$s + Sýna upprunalegt + Þýðing mistókst. Mögulega hefur kerfisstjórinn ekki virkjað þýðingar á þessum netþjóni, eða að netþjónninn sé keyrður á eldri útgáfu Mastodon þar sem þýðingar séu ekki studdar. + Gagnaleynd og útbreiðsla + Hafa notandasnið og færslur með í reikniritum leitar + Hafa opinberar færslur með í leitarniðurstöðum + + %,d þátttakandi + %,d þátttakendur + + + %,d færsla í dag + %,d færslur í dag + diff --git a/mastodon/src/main/res/values-it-rIT/strings.xml b/mastodon/src/main/res/values-it-rIT/strings.xml index 0e93b619ac..67b70f50f1 100644 --- a/mastodon/src/main/res/values-it-rIT/strings.xml +++ b/mastodon/src/main/res/values-it-rIT/strings.xml @@ -582,4 +582,19 @@ %dmin fa %do fa %dg fa + + Traduci da %s + + Tradotto da %1$s utilizzando %2$s + Mostra originale + Traduzione fallita. Forse l\'amministratore non ha abilitato le traduzioni su questo server, o su questo server è in esecuzione una versione precedente di Mastodon in cui le traduzioni non sono ancora supportate. + Includi i post pubblici nei risultati di ricerca + + %,d participante + %,d partecipanti + + + %,d post oggi + %,d post oggi + diff --git a/mastodon/src/main/res/values-iw-rIL/strings.xml b/mastodon/src/main/res/values-iw-rIL/strings.xml index ee40205ae3..c66e4bc67a 100644 --- a/mastodon/src/main/res/values-iw-rIL/strings.xml +++ b/mastodon/src/main/res/values-iw-rIL/strings.xml @@ -88,4 +88,6 @@ + + diff --git a/mastodon/src/main/res/values-ja-rJP/strings.xml b/mastodon/src/main/res/values-ja-rJP/strings.xml index a0a78e100b..1fa201e221 100644 --- a/mastodon/src/main/res/values-ja-rJP/strings.xml +++ b/mastodon/src/main/res/values-ja-rJP/strings.xml @@ -553,4 +553,19 @@ %d 分前 %d 時間前 %d 日前 + + %s から翻訳 + + %2$s による %1$s からの翻訳 + 原文を表示 + 翻訳に失敗しました。このサーバーは翻訳機能を有効にしていないか、翻訳機能のない旧バージョンの Mastodon を実行しているのかもしれません。 + プライバシーとつながりやすさ + アカウントを見つけやすくする + 公開投稿を検索できるようにする + + 参加者 %d 人 + + + 今日の投稿 %,d 件 + diff --git a/mastodon/src/main/res/values-kab/strings.xml b/mastodon/src/main/res/values-kab/strings.xml index 7746c8ea89..c3ff274e09 100644 --- a/mastodon/src/main/res/values-kab/strings.xml +++ b/mastodon/src/main/res/values-kab/strings.xml @@ -242,4 +242,6 @@ + + diff --git a/mastodon/src/main/res/values-ko-rKR/strings.xml b/mastodon/src/main/res/values-ko-rKR/strings.xml index ef3d9feee4..42dd4f9a35 100644 --- a/mastodon/src/main/res/values-ko-rKR/strings.xml +++ b/mastodon/src/main/res/values-ko-rKR/strings.xml @@ -300,4 +300,6 @@ + + diff --git a/mastodon/src/main/res/values-my-rMM/strings.xml b/mastodon/src/main/res/values-my-rMM/strings.xml index 2dde9fb61e..38ecd28a27 100644 --- a/mastodon/src/main/res/values-my-rMM/strings.xml +++ b/mastodon/src/main/res/values-my-rMM/strings.xml @@ -180,4 +180,6 @@ + + diff --git a/mastodon/src/main/res/values-nl-rNL/strings.xml b/mastodon/src/main/res/values-nl-rNL/strings.xml index 6bb8b36377..d9132aa9a5 100644 --- a/mastodon/src/main/res/values-nl-rNL/strings.xml +++ b/mastodon/src/main/res/values-nl-rNL/strings.xml @@ -85,6 +85,10 @@ %d dag resterend %d dagen resterend + + %,d stem + %,d stemmen + Gesloten Account negeren Het negeren van %s bevestigen @@ -383,6 +387,7 @@ Welkom bij Mastodon Mastodon is een gedecentraliseerd sociaal netwerk, wat betekent dat geen enkel bedrijf het controleert. Het bestaat uit veel onafhankelijk opererende servers, allemaal met elkaar verbonden. Wat zijn servers? + Elk Mastodon-account wordt gehost op een server – elk met diens eigen waarden, regels en beheerders. Het maakt niet uit welke server je kiest, je kunt mensen op elke server volgen en ermee communiceren. Koppeling aan het openen… Deze koppeling wordt niet ondersteund in de app Bij alle accounts afmelden @@ -401,6 +406,7 @@ Bestand %d%% geüpload Enquete-optie toevoegen + Lengte enquête Stijl Kies een Meerkeuze @@ -409,9 +415,38 @@ Alternatieve tekst Hulp Wat is alternatieve tekst? + Alt tekst biedt afbeeldingsbeschrijvingen voor mensen met een visiuele beperking en verbindingen met een lage bandbreedte of voor mensen die naar extra context zoeken.\n\nJe kunt de toegankelijkheid en het begrip voor iedereen verbeteren door duidelijk, beknopt en objectief te schrijven.\n\n
  • Beschrijf belangrijke elementen
  • \n
  • Vat tekst in afbeeldingen samen
  • \n
  • Gebruik de reguliere zinsopbouw
  • \n
  • Vermijd overbodige informatie
  • \n
  • Focus op trends en belangrijke bevindingen in complexe beelden (zoals diagrammen of kaarten)
\n
+ Bericht bewerken + Geen geverifieerde koppeling + Emoji doorzoeken + Vind wie je zoekt + Deze zoektermen leveren geen resultaat op + Taal + Standaard + Systeem + Taal detecteren + Kan taal niet detecteren + Gedetecteerd + Media verborgen + Bericht verborgen + Bericht rapporteren + De account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van dit rapport naar toesturen? + Doorsturen naar %s + Gerapporteerd + Als je hun berichten niet meer in je home feed wilt zien, moet je deze persoon niet meer volgen. + %s gedempt + Je hebt deze gebruiker al geblokkeerd, dus je hoeft niets meer te doen terwijl we je rapport beoordelen. + Je hebt deze gebruiker al geblokkeerd, dus er is niets anders dat je hoeft te doen.\n\nBedankt voor het helpen om Mastodon een veilige plek voor iedereen te behouden! + %s geblokkeerd + Alles als gelezen markeren + Weergave + Filters + Overzicht, regels, moderators + Over %s + Standaard taal bericht Alt-tekst-herinneringen toevoegen Vraag voor ontvolgen iemand Vragen voor boosten @@ -437,6 +472,44 @@ Eindigt %s + Notificaties worden hervat %s. + Nu hervatten + Ga naar de meldingsinstellingen + Over + Regels + Beheerder + Bericht aan beheerder + Schakel meldingen vanuit instellingen van uw apparaat in om van overal updates te zien. + Nog meer instellingen + Inhoudswaarschuwingen tonen + Als gevoelig gemarkeerde media verbergen + Aantal berichtinteracties + Aangepaste emoji in weergavenamen + + in %d seconde + in %d seconden + + + in %d minuut + in %d minuten + + + in %d uur + in %d uur + + + %d uur geleden + %d uur geleden + + Media zonder alt-tekst + + %s van je afbeeldingen mist alt-tekst. Toch plaatsen? + %s van je afbeeldingen missen alt-tekst. Toch plaatsen? + + + %s van je mediabijlagen mist alt-tekst. Toch plaatsen? + %s van je mediabijlagen missen alt-tekst. Toch plaatsen? + Een Twee Drie @@ -444,7 +517,83 @@ Plaatsen %s ontvolgen? + Actief + Inactief + Filter toevoegen + Filter bewerken + Duur + Gedempte woorden + Gedempt van + Toon met inhoudswaarschuwing + Berichten die met dit filter overeenkomen, maar achter een inhoudswaarschuwing zitten toch tonen + Filter verwijderen + Voor altijd + Eindigt %s + + %d gedempt woord of zin + %d gedempte woorden of zinnen + + %1$s en %2$s + %1$s, %2$s en %3$s + %1$s, %2$s en %3$d meer + Startpagina & lijsten + Meldingen + Openbare tijdlijnen + Threads & antwoorden + Profielen + Titel + Filter ‘%s’ verwijderen? + Dit filter zal op al uw apparaten uit uw account worden verwijderd. + Gedempte woord toevoegen + Gedempte woord bewerken + Toevoegen + Woord of zin + Woorden zijn niet hoofdlettergevoelig en komen alleen overeen met volledige woorden.\n\nAls je op de term ‘Apple’ filtert, verbergt het berichten die ‘apple’ of ‘aPpLe’ bevatten, maar niet ‘ananas’. + ‘%s’ verwijderen? + Selecteren + Alles selecteren + Filter tijdsduur + Aangepast + + %d woord verwijderen? + %d woorden verwijderen? + + + %d geselecteerd + %d geselecteerd + + Mag niet leeg zijn + Al in de lijst + App-update voltooid + Versie %s + Downloaden (%d%%) + Komt overeen met filter ‘%s’ + Mastodon doorzoeken + Alles wissen + URL in Mastodon openen + Berichten met ‘%s’ + Ga naar %s + Berichten met ‘%s’ + Personen met ‘%s’ + %ds geleden + %dm geleden + %du geleden + %d dagen geleden + + Vanuit het %s vertalen + + Vertaald vanuit het %1$s door %2$s + Origineel bekijken + Vertaling mislukt. Mogelijk heeft de serverbeheerder vertalingen niet ingeschakeld op deze server, of draait deze server een oude versie van Mastodon waar vertalingen nog niet worden ondersteund. + + %d deelnemer + %d deelnemers + + + %,d bericht vandaag + %,d berichten vandaag + diff --git a/mastodon/src/main/res/values-no-rNO/strings.xml b/mastodon/src/main/res/values-no-rNO/strings.xml index 01f9d98b0c..4a0f48a105 100644 --- a/mastodon/src/main/res/values-no-rNO/strings.xml +++ b/mastodon/src/main/res/values-no-rNO/strings.xml @@ -581,4 +581,6 @@ %dm siden %dt siden %dd siden + + diff --git a/mastodon/src/main/res/values-oc-rFR/strings.xml b/mastodon/src/main/res/values-oc-rFR/strings.xml index 9795846575..9c200b23d2 100644 --- a/mastodon/src/main/res/values-oc-rFR/strings.xml +++ b/mastodon/src/main/res/values-oc-rFR/strings.xml @@ -79,4 +79,6 @@ + + diff --git a/mastodon/src/main/res/values-pl-rPL/strings.xml b/mastodon/src/main/res/values-pl-rPL/strings.xml index 8ec8593069..f97cb85e53 100644 --- a/mastodon/src/main/res/values-pl-rPL/strings.xml +++ b/mastodon/src/main/res/values-pl-rPL/strings.xml @@ -457,4 +457,6 @@ + + diff --git a/mastodon/src/main/res/values-pt-rBR/strings.xml b/mastodon/src/main/res/values-pt-rBR/strings.xml index afc5bb99ff..f6a4a8127c 100644 --- a/mastodon/src/main/res/values-pt-rBR/strings.xml +++ b/mastodon/src/main/res/values-pt-rBR/strings.xml @@ -8,7 +8,7 @@ OK Preparando para autenticação… Finalizando autenticação… - %s impulsionado + %s impulsionou Em resposta à %s Notificações %s seguiu você @@ -571,4 +571,6 @@ %dm atrás %dh atrás %dd atrás + + diff --git a/mastodon/src/main/res/values-pt-rPT/strings.xml b/mastodon/src/main/res/values-pt-rPT/strings.xml index 46ac5c7e15..ce8553f1fa 100644 --- a/mastodon/src/main/res/values-pt-rPT/strings.xml +++ b/mastodon/src/main/res/values-pt-rPT/strings.xml @@ -234,4 +234,6 @@ + + diff --git a/mastodon/src/main/res/values-ro-rRO/strings.xml b/mastodon/src/main/res/values-ro-rRO/strings.xml index 6fd6f75503..e010dcaf9e 100644 --- a/mastodon/src/main/res/values-ro-rRO/strings.xml +++ b/mastodon/src/main/res/values-ro-rRO/strings.xml @@ -48,4 +48,6 @@ + + diff --git a/mastodon/src/main/res/values-ru-rRU/strings.xml b/mastodon/src/main/res/values-ru-rRU/strings.xml index db71441342..2e5bb9bfd3 100644 --- a/mastodon/src/main/res/values-ru-rRU/strings.xml +++ b/mastodon/src/main/res/values-ru-rRU/strings.xml @@ -640,4 +640,25 @@ %d мин. назад %d ч. назад %d д. назад + + Перевести %s + + %1$s переведён с помощью %2$s + Показать оригинал + Перевод не удался. Возможно, администратор не включил переводы на этом сервере или на этом сервере установлена ​​более старая версия Mastodon, где переводы еще не поддерживаются. + Приватность и доступ + Показывать профиль и посты в алгоритмах рекомендаций + Включить публичные посты в результаты поиска + + %,d участник + %,d участника + %,d участников + %,d участников + + + %,d пост сегодня + %,d поста сегодня + %,d постов сегодня + %,d постов сегодня + diff --git a/mastodon/src/main/res/values-si-rLK/strings.xml b/mastodon/src/main/res/values-si-rLK/strings.xml index 44b67c364b..d26a76f2ac 100644 --- a/mastodon/src/main/res/values-si-rLK/strings.xml +++ b/mastodon/src/main/res/values-si-rLK/strings.xml @@ -75,4 +75,6 @@ + + diff --git a/mastodon/src/main/res/values-sl-rSI/strings.xml b/mastodon/src/main/res/values-sl-rSI/strings.xml index cb81f96312..b7a7f1d30b 100644 --- a/mastodon/src/main/res/values-sl-rSI/strings.xml +++ b/mastodon/src/main/res/values-sl-rSI/strings.xml @@ -274,6 +274,8 @@ To so objave, ki plenijo pozornost po Mastodonu. To so novice, o katerih se govori na Mastodonu. + To so vse objave vseh uporabnikov na vašem strežniku (%s). + Glede na to, komu sledite, vam bodo ti računi všeč. Pokaži nove objave Naloži manjkajoče objave Sledijo nazaj @@ -455,8 +457,11 @@ O programu %s Privzeti jezik objave + Vprašaj pred brisanjem objav Premor za vse Izklopljeno + Kdor koli + Ljudje, ki vam sledijo Ljudje, ki jim sledite Nihče Omembe in odgovori @@ -504,6 +509,7 @@ Profili Naslov Ali želite izbrisati filter »%s«? + Ta filter bo izbrisan iz vašega računa na vseh vaših napravah. Dodaj utišano besedo Uredi utišano besedo Dodaj @@ -532,4 +538,13 @@ pred %d meseci pred %dh urami pred %d dnemi + + Prevedi iz jezika: %s + + Prevedeno iz %1$s s pomočjo %2$s + Pokaži izvirnik + Prevod je spodletel. Morda skrbnik ni omogočil prevajanja na tem strežniku ali pa strežnik teče na starejši različici Masotodona, na kateri prevajanje še ni podprto. + Zasebnost in dosegljivost + Izpostavljaj profile in objave v algoritmih odkrivanja + Med zadetke iskanja vključi javne objave diff --git a/mastodon/src/main/res/values-sv-rSE/strings.xml b/mastodon/src/main/res/values-sv-rSE/strings.xml index f3e7f6a20e..1fecf34247 100644 --- a/mastodon/src/main/res/values-sv-rSE/strings.xml +++ b/mastodon/src/main/res/values-sv-rSE/strings.xml @@ -12,6 +12,7 @@ Som svar på %s Notiser %s följde dig + %s favoritmarkerade ditt inlägg Dela Inställningar Publicera @@ -229,6 +230,7 @@ Filen sparad Laddar ner… + Du kanske gillar dessa konton baserat på andra konton du följer. Se nya inlägg Ladda saknade inlägg Följ tillbaka @@ -353,6 +355,8 @@ %s video %s ljud %s fil + Bild + Video Ljud GIF Fil @@ -383,6 +387,7 @@ Om Regler Administratör + Anpassad emoji i visningsnamn om %d timme om %d timmar @@ -391,6 +396,9 @@ %d timme sedan %d timmar sedan + Två + Tre + Fyra Aktiv Inaktiv @@ -401,12 +409,19 @@ %1$s och %2$s %1$s, %2$s och %3$s Profiler + Detta filter kommer raderas från ditt konto på alla dina enheter. Lägg till Ord eller fras Välj Välj alla + + Radera %d ord? + Radera %d ord? + Version %s + Matchar filter \"%s\" + Sök i Mastodon Rensa alla Öppna URL i Mastodon Inlägg med \"%s\" @@ -416,4 +431,7 @@ %dm sedan %dt sedan %dd sedan + + Översätt från %s + diff --git a/mastodon/src/main/res/values-th-rTH/strings.xml b/mastodon/src/main/res/values-th-rTH/strings.xml index ae9fb1fdce..7acb8fb8a7 100644 --- a/mastodon/src/main/res/values-th-rTH/strings.xml +++ b/mastodon/src/main/res/values-th-rTH/strings.xml @@ -553,4 +553,19 @@ %d นาทีที่แล้ว %d ชั่วโมงที่แล้ว %d วันที่แล้ว + + แปลจาก %s + + แปลจาก %1$s โดยใช้ %2$s + แสดงดั้งเดิม + การแปลล้มเหลว บางทีผู้ดูแลอาจไม่ได้เปิดใช้งานการแปลในเซิร์ฟเวอร์นี้หรือเซิร์ฟเวอร์นี้กำลังใช้ Mastodon รุ่นเก่ากว่าที่ยังไม่รองรับการแปล + ความเป็นส่วนตัวและการเข้าถึง + แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ + รวมโพสต์สาธารณะในผลลัพธ์การค้นหา + + %,d ผู้มีส่วนร่วม + + + %,d โพสต์วันนี้ + diff --git a/mastodon/src/main/res/values-tr-rTR/strings.xml b/mastodon/src/main/res/values-tr-rTR/strings.xml index aaed909f91..bbbfc5692f 100644 --- a/mastodon/src/main/res/values-tr-rTR/strings.xml +++ b/mastodon/src/main/res/values-tr-rTR/strings.xml @@ -387,6 +387,7 @@ Mastodon\'a hoş geldiniz Mastodon merkezi olmayan bir sosyal ağdır, yani onu tek bir şirket kontrol etmiyor. Hepsi birbirine bağlı, bağımsız olarak çalışan birçok sunucudan oluşur. Sunucular nedir? + Her Mastodon hesabı bir sunucuda barındırılır - her birinin kendi değerleri, kuralları ve yöneticileri vardır. Hangisini seçerseniz seçin, herhangi bir sunucudaki insanları takip edebilir ve onlarla etkileşime geçebilirsiniz. Bağlantı açılıyor… Bu bağlantı uygulamada desteklenmiyor Tüm hesaplardan çıkış yap @@ -581,4 +582,21 @@ %ddk önce %dsa önce %dg önce + + %s dilinden çevrildi + + %2$s kullanılarak %1$s dilinden çevrildi + Özgün içeriği göster + Çeviri başarısız oldu. Yönetici bu sunucuda çevirileri etkinleştirmemiş olabilir veya bu sunucu çevirilerin henüz desteklenmediği eski bir Mastodon sürümünü çalıştırıyor olabilir. + Gizlilik ve erişim + Profil ve gönderileri keşif algoritmalarında kullan + Herkese açık gönderileri arama sonuçlarına ekle + + %d katılımcılar + %d katılımcılar + + + %,d bunu gönder + %,d bunları gönder + diff --git a/mastodon/src/main/res/values-uk-rUA/strings.xml b/mastodon/src/main/res/values-uk-rUA/strings.xml index ed56e0aa68..d2080f40e2 100644 --- a/mastodon/src/main/res/values-uk-rUA/strings.xml +++ b/mastodon/src/main/res/values-uk-rUA/strings.xml @@ -425,6 +425,7 @@ Вітаємо у Mastodon Mastodon - це децентралізована соціальна мережа, тобто жодна компанія не контролює її. Вона складається з багатьох незалежних серверів, які з\'єднані між собою. Що таке сервери? + Кожен обліковий запис Mastodon розміщений на сервері - кожен сервер має свої цінності, правила, й & адмінів. Немає різниці, який ви оберете, ви зможете підписуватися та спілкуватися з користувачами з будь-якого. Відкриття посилання… Це посилання не підтримується застосунком Вийти з усіх акаунтів @@ -639,4 +640,25 @@ %dхв. тому %dгод. тому %dд. тому + + Перекласти з %s + + Перекладено з %1$s за допомогою %2$s + Показати оригінал + Не вдалося виконати переклад. Можливо, адміністратор не активував переклади на цьому сервері або цей сервер використовує стару версію Mastodon, де переклади ще не підтримуються. + Приватність і досяжність + Враховувати профіль та дописи в алгоритмах пошуку + Включити загальнодоступні дописи в результати пошуку + + %,d учасник + %,d учасники + %,d учасників + %,d учасника + + + %,d допис сьогодні + %,d дописи сьогодні + %,d дописів сьогодні + %,d дописа сьогодні + diff --git a/mastodon/src/main/res/values-ur-rIN/strings.xml b/mastodon/src/main/res/values-ur-rIN/strings.xml index 0994605c78..2d5f561272 100644 --- a/mastodon/src/main/res/values-ur-rIN/strings.xml +++ b/mastodon/src/main/res/values-ur-rIN/strings.xml @@ -20,4 +20,6 @@ + + diff --git a/mastodon/src/main/res/values-vi-rVN/strings.xml b/mastodon/src/main/res/values-vi-rVN/strings.xml index f83b2961ab..1b74201cfe 100644 --- a/mastodon/src/main/res/values-vi-rVN/strings.xml +++ b/mastodon/src/main/res/values-vi-rVN/strings.xml @@ -266,7 +266,7 @@ %,d lượt thích - %,đăng lại + %,d đăng lại %1$s qua %2$s vừa xong @@ -553,4 +553,16 @@ %dp %dh %d ngày + + Dịch từ %s + + Dịch từ %1$s bằng %2$s + Bản gốc + Dịch không thành công. Có thể quản trị viên chưa bật dịch trên máy chủ này hoặc máy chủ này đang chạy phiên bản cũ hơn của Mastodon chưa hỗ trợ dịch. + + %,d người thảo luận + + + %,d tút hôm nay + diff --git a/mastodon/src/main/res/values-zh-rCN/strings.xml b/mastodon/src/main/res/values-zh-rCN/strings.xml index 547e92d4a2..7eed1d1dd7 100644 --- a/mastodon/src/main/res/values-zh-rCN/strings.xml +++ b/mastodon/src/main/res/values-zh-rCN/strings.xml @@ -552,4 +552,13 @@ %d 分钟前 %d 小时前 %d 天前 + + 从 %s 翻译 + + 从 %1$s 翻译成 %2$s + 显示原文 + 翻译失败。此服务器运行的 Mastodon 版本不支持翻译功能,或管理员未将翻译功能开启。 + 隐私与可达性 + 在发现算法中展示您的个人资料和嘟文 + 将您的公开嘟文纳入搜索范围 diff --git a/mastodon/src/main/res/values-zh-rTW/strings.xml b/mastodon/src/main/res/values-zh-rTW/strings.xml index 720b445e0a..b0575fe38c 100644 --- a/mastodon/src/main/res/values-zh-rTW/strings.xml +++ b/mastodon/src/main/res/values-zh-rTW/strings.xml @@ -553,4 +553,19 @@ %d 分鐘前 %d 小時前 %d 天前 + + 翻譯自 %s + + 透過 %2$s 翻譯 %1$s + 顯示原文 + 翻譯失敗。也許管理員未於此伺服器啟用翻譯功能,或此伺服器為未支援翻譯功能之舊版本 Mastodon。 + 隱私權及觸及 + 於探索演算法中推薦個人檔案及嘟文 + 允許公開嘟文顯示於搜尋結果中 + + %,d 位參與者 + + + 本日共 %,d 則嘟文 + diff --git a/mastodon/src/main/res/values/styles.xml b/mastodon/src/main/res/values/styles.xml index 1d9a6892e7..234daf569a 100644 --- a/mastodon/src/main/res/values/styles.xml +++ b/mastodon/src/main/res/values/styles.xml @@ -36,6 +36,7 @@ true ?colorM3PrimaryContainer @drawable/ic_fluent_dismiss_24_regular + ?colorM3Background ?colorM3Background @@ -72,6 +73,7 @@ true ?colorM3PrimaryContainer @drawable/ic_fluent_dismiss_24_regular + #1FE3E3E3 #F2B8B5 @@ -381,4 +383,8 @@ + + \ No newline at end of file diff --git a/tools/VerifyTranslatedStringFormatting.java b/tools/VerifyTranslatedStringFormatting.java new file mode 100644 index 0000000000..2aeb22d193 --- /dev/null +++ b/tools/VerifyTranslatedStringFormatting.java @@ -0,0 +1,116 @@ +// run: java tools/VerifyTranslatedStringFormatting.java +// Reads all localized strings and makes sure they contain valid formatting placeholders matching the original English strings to avoid crashes. + +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.io.*; +import java.util.*; +import java.util.regex.*; + +public class VerifyTranslatedStringFormatting{ + // %[argument_index$][flags][width][.precision][t]conversion + private static final String formatSpecifier="%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; + private static final Pattern fsPattern=Pattern.compile(formatSpecifier); + + private static HashMap> placeholdersInStrings=new HashMap<>(); + private static int errorCount=0; + + public static void main(String[] args) throws Exception{ + DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder=factory.newDocumentBuilder(); + Document doc; + try(FileInputStream in=new FileInputStream("mastodon/src/main/res/values/strings.xml")){ + doc=builder.parse(in); + } + NodeList list=doc.getDocumentElement().getChildNodes(); // why does this stupid NodeList thing exist at all? + for(int i=0;i placeholders=new ArrayList<>(); + Matcher matcher=fsPattern.matcher(value); + while(matcher.find()){ + placeholders.add(matcher.group()); + } + placeholdersInStrings.put(name, placeholders); + } + } + for(File file:new File("mastodon/src/main/res").listFiles()){ + if(file.getName().startsWith("values-")){ + File stringsXml=new File(file, "strings.xml"); + if(stringsXml.exists()){ + processFile(stringsXml); + } + } + } + if(errorCount>0){ + System.err.println("Found "+errorCount+" problems in localized strings"); + System.exit(1); + } + } + + private static void processFile(File file) throws Exception{ + DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder=factory.newDocumentBuilder(); + Document doc; + try(FileInputStream in=new FileInputStream(file)){ + doc=builder.parse(in); + } + NodeList list=doc.getDocumentElement().getChildNodes(); + for(int i=0;i placeholders){ + for(String placeholder:placeholders){ + if(placeholder.equals("%,d")){ + // %,d and %d are interchangeable but %,d provides nicer formatting + if(!str.contains(placeholder) && !str.contains("%d")) + return false; + }else if(!str.contains(placeholder)){ + return false; + } + } + return true; + } +} \ No newline at end of file