diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 5be683a2f..79547e49c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -101,6 +101,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag private static final int MEDIA_PICK_RESULT = 1; private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1; private static final int MEDIA_SIZE_UNKNOWN = -1; + private static final int COMPOSE_SUCCESS = -1; private String inReplyToId; private EditText textEditor; @@ -122,6 +123,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag private ImageButton pickBtn; private Button nsfwBtn; private ProgressBar postProgress; + private ImageButton visibilityBtn; private static class QueuedMedia { enum Type { @@ -341,17 +343,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag floatingBtn = (Button) findViewById(R.id.floating_btn); pickBtn = (ImageButton) findViewById(R.id.compose_photo_pick); nsfwBtn = (Button) findViewById(R.id.action_toggle_nsfw); - final ImageButton visibilityBtn = (ImageButton) findViewById(R.id.action_toggle_visibility); + visibilityBtn = (ImageButton) findViewById(R.id.action_toggle_visibility); floatingBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - pickBtn.setClickable(false); - nsfwBtn.setClickable(false); - visibilityBtn.setClickable(false); - floatingBtn.setEnabled(false); - - postProgress.setVisibility(View.VISIBLE); sendStatus(); } }); @@ -567,6 +563,20 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag } } + private void disableButtons() { + pickBtn.setClickable(false); + nsfwBtn.setClickable(false); + visibilityBtn.setClickable(false); + floatingBtn.setEnabled(false); + } + + private void enableButtons() { + pickBtn.setClickable(true); + nsfwBtn.setClickable(true); + visibilityBtn.setClickable(true); + floatingBtn.setEnabled(true); + } + private void setStatusVisibility(String visibility) { statusVisibility = visibility; switch (visibility) { @@ -615,6 +625,18 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag updateVisibleCharactersLeft(); } + void setStateToReadying() { + statusAlreadyInFlight = true; + disableButtons(); + postProgress.setVisibility(View.VISIBLE); + } + + void setStateToNotReadying() { + postProgress.setVisibility(View.INVISIBLE); + statusAlreadyInFlight = false; + enableButtons(); + } + private void sendStatus() { if (statusAlreadyInFlight) { return; @@ -624,9 +646,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag if (statusHideText) { spoilerText = contentWarningEditor.getText().toString(); } - if (contentText.length() + spoilerText.length() <= STATUS_CHARACTER_LIMIT) { - statusAlreadyInFlight = true; + int characterCount = contentText.length() + spoilerText.length(); + if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) { + setStateToReadying(); readyStatus(contentText, statusVisibility, statusMarkSensitive, spoilerText); + } else if (characterCount <= 0) { + textEditor.setError(getString(R.string.error_empty)); } else { textEditor.setError(getString(R.string.error_compose_character_limit)); } @@ -816,13 +841,13 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag private void onSendSuccess() { Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(R.string.confirmation_send), Snackbar.LENGTH_SHORT); bar.show(); + setResult(COMPOSE_SUCCESS); finish(); } private void onSendFailure() { - postProgress.setVisibility(View.INVISIBLE); textEditor.setError(getString(R.string.error_generic)); - statusAlreadyInFlight = false; + setStateToNotReadying(); } private void readyStatus(final String content, final String visibility, final boolean sensitive, @@ -857,7 +882,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag @Override protected void onCancelled() { removeAllMediaFromQueue(); - statusAlreadyInFlight = false; + setStateToNotReadying(); super.onCancelled(); } }; @@ -882,7 +907,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag readyStatus(content, visibility, sensitive, spoilerText); } }); - statusAlreadyInFlight = false; + setStateToNotReadying(); } private void onMediaPick() { diff --git a/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java index 6ebe217ff..e39ca44af 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java @@ -23,6 +23,8 @@ class FooterViewHolder extends RecyclerView.ViewHolder { FooterViewHolder(View itemView) { super(itemView); ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar); - progressBar.setIndeterminate(true); + if (progressBar != null) { + progressBar.setIndeterminate(true); + } } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index bd4326ed2..4191ba172 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -66,6 +66,7 @@ import retrofit2.Response; public class MainActivity extends BaseActivity { private static final String TAG = "MainActivity"; // logging tag and Volley request tag + protected static int COMPOSE_RESULT = 1; private String loggedInAccountId; private String loggedInAccountUsername; @@ -99,7 +100,7 @@ public class MainActivity extends BaseActivity { @Override public void onClick(View v) { Intent intent = new Intent(getApplicationContext(), ComposeActivity.class); - startActivity(intent); + startActivityForResult(intent, COMPOSE_RESULT); } }); @@ -471,6 +472,17 @@ public class MainActivity extends BaseActivity { Log.e(TAG, "Failed to fetch user info. " + exception.getMessage()); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) { + TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter(); + if (adapter.getCurrentFragment() instanceof SFragment) { + ((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus(); + } + } + super.onActivityResult(requestCode, resultCode, data); + } + @Override public void onBackPressed() { if(drawer != null && drawer.isDrawerOpen()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java index ea45b0782..64ecb722a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java @@ -42,9 +42,16 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2; private static final int VIEW_TYPE_FOLLOW = 3; + enum FooterState { + EMPTY, + END, + LOADING + } + private List notifications; private StatusActionListener statusListener; private NotificationActionListener notificationActionListener; + private FooterState footerState = FooterState.END; NotificationsAdapter(StatusActionListener statusListener, NotificationActionListener notificationActionListener) { @@ -54,6 +61,15 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe this.notificationActionListener = notificationActionListener; } + + public void setFooterState(FooterState newFooterState) { + FooterState oldValue = footerState; + footerState = newFooterState; + if (footerState != oldValue) { + notifyItemChanged(notifications.size()); + } + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { @@ -64,8 +80,24 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe return new StatusViewHolder(view); } case VIEW_TYPE_FOOTER: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_footer, parent, false); + View view; + switch (footerState) { + default: + case LOADING: + view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_footer, parent, false); + break; + case END: { + view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_footer_end, parent, false); + break; + } + case EMPTY: { + view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_footer_empty, parent, false); + break; + } + } return new FooterViewHolder(view); } case VIEW_TYPE_STATUS_NOTIFICATION: { diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java index 152a67eed..b754e6c41 100644 --- a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java @@ -144,6 +144,10 @@ public class NotificationsFragment extends SFragment implements private void sendFetchNotificationsRequest(final String fromId, String uptoId) { MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI; + if (fromId != null || adapter.getItemCount() <= 1) { + adapter.setFooterState(NotificationsAdapter.FooterState.LOADING); + } + listCall = api.notifications(fromId, uptoId, null); listCall.enqueue(new Callback>() { @@ -192,6 +196,11 @@ public class NotificationsFragment extends SFragment implements } else { adapter.update(notifications); } + if (notifications.size() == 0 && adapter.getItemCount() == 1) { + adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); + } else if (fromId != null) { + adapter.setFooterState(NotificationsAdapter.FooterState.END); + } swipeRefreshLayout.setRefreshing(false); } diff --git a/app/src/main/java/com/keylesspalace/tusky/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/SFragment.java index f18f07f04..b810468a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/SFragment.java @@ -44,9 +44,10 @@ import retrofit2.Callback; * adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also * overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear * up what needs to be where. */ -public class SFragment extends BaseFragment { +public abstract class SFragment extends BaseFragment { protected String loggedInAccountId; protected String loggedInUsername; + protected static int COMPOSE_RESULT = 1; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -79,11 +80,23 @@ public class SFragment extends BaseFragment { intent.putExtra("reply_visibility", replyVisibility); intent.putExtra("content_warning", contentWarning); intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0])); - startActivity(intent); + startActivityForResult(intent, COMPOSE_RESULT); + } + + public void onSuccessfulStatus() { + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) { + onSuccessfulStatus(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } } protected void reblog(final Status status, final boolean reblog, - final RecyclerView.Adapter adapter, final int position) { + final RecyclerView.Adapter adapter, final int position) { String id = status.getActionableId(); Callback cb = new Callback() { @@ -182,8 +195,8 @@ public class SFragment extends BaseFragment { callList.add(call); } - protected void more(Status status, View view, final AdapterItemRemover adapter, - final int position) { + protected void more(final Status status, View view, final AdapterItemRemover adapter, + final int position) { final String id = status.getActionableId(); final String accountId = status.getActionableStatus().account.id; final String accountUsename = status.getActionableStatus().account.username; @@ -201,12 +214,25 @@ public class SFragment extends BaseFragment { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { - case R.id.status_share: { + case R.id.status_share_content: { + StringBuilder sb = new StringBuilder(); + sb.append(status.account.username); + sb.append(" - "); + sb.append(status.content.toString()); + + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString()); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to))); + return true; + } + case R.id.status_share_link: { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl); sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_to))); + startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_link_to))); return true; } case R.id.status_block: { diff --git a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java b/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java index fea024558..7af6d13f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java @@ -65,20 +65,43 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover { notifyItemRemoved(position); } - int insertStatus(Status status) { + int setStatus(Status status) { + if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) { + // Do not add this status on refresh, it's already in there. + statuses.set(statusIndex, status); + return statusIndex; + } int i = statusIndex; statuses.add(i, status); notifyItemInserted(i); return i; } - void addAncestors(List ancestors) { + void setContext(List ancestors, List descendants) { + Status mainStatus = null; + + // In case of refresh, remove old ancestors and descendants first. We'll remove all blindly, + // as we have no guarantee on their order to be the same as before + int old_size = statuses.size(); + if (old_size > 0) { + mainStatus = statuses.get(statusIndex); + statuses.clear(); + notifyItemRangeRemoved(0, old_size); + } + + // Insert newly fetched ancestors statusIndex = ancestors.size(); statuses.addAll(0, ancestors); notifyItemRangeInserted(0, statusIndex); - } - void addDescendants(List descendants) { + if (mainStatus != null) { + // In case we needed to delete everything (which is way easier than deleting + // everything except one), re-insert the remaining status here. + statuses.add(statusIndex, mainStatus); + notifyItemInserted(statusIndex); + } + + // Insert newly fetched descendants int end = statuses.size(); statuses.addAll(descendants); notifyItemRangeInserted(end, descendants.size()); diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java index 8f8fc5460..a6c1367ee 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java @@ -30,8 +30,15 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover private static final int VIEW_TYPE_STATUS = 0; private static final int VIEW_TYPE_FOOTER = 1; + enum FooterState { + EMPTY, + END, + LOADING + } + private List statuses; private StatusActionListener statusListener; + private FooterState footerState = FooterState.END; TimelineAdapter(StatusActionListener statusListener) { super(); @@ -49,13 +56,37 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover return new StatusViewHolder(view); } case VIEW_TYPE_FOOTER: { - View view = LayoutInflater.from(viewGroup.getContext()) - .inflate(R.layout.item_footer, viewGroup, false); + View view; + switch (footerState) { + default: + case LOADING: + view = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.item_footer, viewGroup, false); + break; + case END: { + view = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.item_footer_end, viewGroup, false); + break; + } + case EMPTY: { + view = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.item_footer_empty, viewGroup, false); + break; + } + } return new FooterViewHolder(view); } } } + public void setFooterState(FooterState newFooterState) { + FooterState oldValue = footerState; + footerState = newFooterState; + if (footerState != oldValue) { + notifyItemChanged(statuses.size()); + } + } + @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if (position < statuses.size()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java index 4f1b6e205..7102764df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java @@ -214,6 +214,10 @@ public class TimelineFragment extends SFragment implements private void sendFetchTimelineRequest(@Nullable final String fromId, @Nullable String uptoId) { MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI; + if (fromId != null || adapter.getItemCount() <= 1) { + adapter.setFooterState(TimelineAdapter.FooterState.LOADING); + } + Callback> cb = new Callback>() { @Override public void onResponse(Call> call, retrofit2.Response> response) { @@ -282,6 +286,11 @@ public class TimelineFragment extends SFragment implements } else { adapter.update(statuses); } + if (statuses.size() == 0 && adapter.getItemCount() == 1) { + adapter.setFooterState(TimelineAdapter.FooterState.EMPTY); + } else if(fromId != null) { + adapter.setFooterState(TimelineAdapter.FooterState.END); + } swipeRefreshLayout.setRefreshing(false); } @@ -299,6 +308,14 @@ public class TimelineFragment extends SFragment implements } } + @Override + public void onSuccessfulStatus() { + if (kind == Kind.HOME || kind == Kind.PUBLIC_FEDERATED || kind == Kind.PUBLIC_LOCAL) { + onRefresh(); + } + super.onSuccessfulStatus(); + } + public void onReply(int position) { super.reply(adapter.getItem(position)); } diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java index af9bc6393..2e81adc33 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java @@ -18,12 +18,27 @@ package com.keylesspalace.tusky; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; +import android.view.ViewGroup; class TimelinePagerAdapter extends FragmentPagerAdapter { + private Fragment currentFragment; + TimelinePagerAdapter(FragmentManager manager) { super(manager); } + public Fragment getCurrentFragment() { + return currentFragment; + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + if (getCurrentFragment() != object) { + currentFragment = ((Fragment) object); + } + super.setPrimaryItem(container, position, object); + } + @Override public Fragment getItem(int i) { switch (i) { diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java index 4101b44a1..39baf8ee6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java @@ -21,6 +21,7 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -34,9 +35,11 @@ import com.keylesspalace.tusky.entity.StatusContext; import retrofit2.Call; import retrofit2.Callback; -public class ViewThreadFragment extends SFragment implements StatusActionListener { +public class ViewThreadFragment extends SFragment implements + SwipeRefreshLayout.OnRefreshListener, StatusActionListener { private static final String TAG = "ViewThreadFragment"; + private SwipeRefreshLayout swipeRefreshLayout; private RecyclerView recyclerView; private ThreadAdapter adapter; private String thisThreadsStatusId; @@ -56,6 +59,9 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false); Context context = getContext(); + swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout); + swipeRefreshLayout.setOnRefreshListener(this); + recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view); recyclerView.setHasFixedSize(true); LinearLayoutManager layoutManager = new LinearLayoutManager(context); @@ -86,7 +92,7 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene @Override public void onResponse(Call call, retrofit2.Response response) { if (response.isSuccessful()) { - int position = adapter.insertStatus(response.body()); + int position = adapter.setStatus(response.body()); recyclerView.scrollToPosition(position); } else { onThreadRequestFailure(id); @@ -109,10 +115,10 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene @Override public void onResponse(Call call, retrofit2.Response response) { if (response.isSuccessful()) { + swipeRefreshLayout.setRefreshing(false); StatusContext context = response.body(); - adapter.addAncestors(context.ancestors); - adapter.addDescendants(context.descendants); + adapter.setContext(context.ancestors, context.descendants); } else { onThreadRequestFailure(id); } @@ -128,6 +134,7 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene private void onThreadRequestFailure(final String id) { View view = getView(); + swipeRefreshLayout.setRefreshing(false); if (view != null) { Snackbar.make(view, R.string.error_generic, Snackbar.LENGTH_LONG) .setAction(R.string.action_retry, new View.OnClickListener() { @@ -143,6 +150,17 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene } } + public void onRefresh() { + sendStatusRequest(thisThreadsStatusId); + sendThreadRequest(thisThreadsStatusId); + } + + @Override + public void onSuccessfulStatus() { + onRefresh(); + super.onSuccessfulStatus(); + } + public void onReply(int position) { super.reply(adapter.getItem(position)); } diff --git a/app/src/main/res/layout/fragment_view_thread.xml b/app/src/main/res/layout/fragment_view_thread.xml index 732fbb63f..3616a48f6 100644 --- a/app/src/main/res/layout/fragment_view_thread.xml +++ b/app/src/main/res/layout/fragment_view_thread.xml @@ -1,7 +1,13 @@ - \ No newline at end of file + android:layout_height="match_parent"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_footer_empty.xml b/app/src/main/res/layout/item_footer_empty.xml new file mode 100644 index 000000000..1c5606d22 --- /dev/null +++ b/app/src/main/res/layout/item_footer_empty.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_footer_end.xml b/app/src/main/res/layout/item_footer_end.xml new file mode 100644 index 000000000..584de324b --- /dev/null +++ b/app/src/main/res/layout/item_footer_end.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_account.xml b/app/src/main/res/layout/tab_account.xml index 0f314fd45..957bbe3c4 100644 --- a/app/src/main/res/layout/tab_account.xml +++ b/app/src/main/res/layout/tab_account.xml @@ -9,6 +9,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/title" + android:layout_marginTop="8dp" android:layout_centerHorizontal="true" android:textAllCaps="true" android:textStyle="normal|bold" /> diff --git a/app/src/main/res/menu/status_more.xml b/app/src/main/res/menu/status_more.xml index 33617c9d6..48159d7bf 100644 --- a/app/src/main/res/menu/status_more.xml +++ b/app/src/main/res/menu/status_more.xml @@ -2,7 +2,16 @@ + android:title="@string/action_share"> + + + + + + + وقع هناك خطأ. + اسم النطاق غير صالح + اخفقت المصادقة مع مثيل الخادم هذا. + لم يتم العثور على متصفح قابل للإستعمال. + تم رفض التصريح. + يجب أن يكون حجم الملف أقل من 4 ميغابايت. + لا يمكن رفع هذا النوع من الملفات. + تعذر فتح ذاك الملف. + اخفقت عملية الرفع. + الرئيسية + الاشعارات + المحلية + الفدرالية + الخيط + #%s + المشاركات + يتبع + المتابعون + المفضلة + المستخدمون المحظورون + + \@%s + %s عزز + محتوى حساس + اضغط للعرض + اعرض أكثر + اعرض أقل + + نهاية الحالات + نهاية الاشعارات + نهاية الحسابات + + %s عزز تبويقك + %s أعجب بتبويقك + %s يتبعك + + أبلغ عن @%s + تعليقات إضافية ؟ + + أجب + عزز + تفضيل + المزيد + حرر + التسجيل بواسطة ماستدون + خروج + إتبع + إلغاء التتبع + حضر + إلغاء الحظر + أبلغ + إحذف + تبويق + بَوِّق + إعادة المحاولة + اخفي النص وراء تحذير + موافق + إلغاء + إغلاق + عودة + الملف الشخصي + التفضيلات + المفضلة + المستخدمون المحظورون + الخيط + وسائط + إفتح في متصفح + ارسل + إضافة وسائط + شارك + أكتم + إلغاء الكتم + أذكر + NSFW + خيارات + إفتح الدرج + إمسح + + شارك رابط التبويق إلى ... + + ابحث عن حسابات ... + + بَوِّق + تم الإرسال ! + + أي سيرفر ؟ + ما الجديد ؟ + تحذير عن المحتوى + + ماذا نعني بمثيل الخادم ؟ + + تتمة رفع الوسائط + جاري الرفع ... + + الاشعارات + تعديل الاشعارات + دفع الإخطارات + التنبيهات + إعلام بالصوت + إعلام بالاهتزار + إعلام بالضوء + أخطرني عندما + يشار إلي + يتبعني أحد + تعزز وتدفع منشوراتي + يعجب أحد ما بمنشوراتي + المظهر + إستخدم سمةً فاتحة اللون + المتصفح + إخفاء زر المتابعة أثناء تمرير الصفحة + %s أشار إليك + %1$s, %2$s, %3$s و %4$d أخرى + %1$s, %2$s, و %3$s + %1$s و %2$s + %d تفاعلات جديدة + + حساب مقفل + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8804868ad..169b67bb3 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -10,16 +10,22 @@ Bilder und Videos können beide nicht an den Beitrag angehängt werden. Die Mediendatei konnte nicht hochgeladen werden. Mindestens ein Beitrag muss berichtet werden. + Autorisierung fehlgeschlagen. + Ein Fehler ist aufgetreten. + Kein Webbrowser gefunden. + Es konnte kein Login-Token abgerufen werden. Start Benachrichtigungen + Lokal + Föderiert Unterhaltung #%s Beiträge Folgt Folgende Favoriten - Blockierte Nutzer + Blockierte Accounts \@%s %s teilte @@ -68,6 +74,16 @@ Stummschalten Lautschalten Erwähnen + Antworten + Teilen + Favorisieren + Mehr + Thread + Medien + Einstellungen + Drawer öffnen + Löschen + NSFW Teile Toot-URL zu… @@ -104,22 +120,23 @@ Aussehen Benutze helles Theme + Benachrichtigungen + Benachrichtigungseinstellungen + Jemandem meine Posts gefallen + Benachrichtigen wenn + Mir jemand folgt + Ich erwähnt werde + Jemand meine Posts teilt + Browser + Öffne Links in der App + Verstecke Button bei Bildlauf + %s hat dich erwähnt %1$s, %2$s, %3$s und %4$d andere %1$s, %2$s, und %3$s %1$s und %2$s %d neue Interaktionen - Lokal - Föderiert - Benachrichtigungen - Jemandem meine Posts gefallen - Benachrichtigen wenn - Mir jemand folgt - Ich erwähnt werde - Jemand meine Posts boostet - Autorisierung fehlgeschlagen. - Ein Fehler ist aufgetreten. - Kein Webbrowser gefunden. - Es konnte kein Login-Token abgerufen werden. - + Gesperrter Account + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 05fa87504..0751e6a14 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,30 +1,30 @@ - Une erreur s\'est produite. + Une erreur s’est produite. Le domaine est invalide - L\'application n\'a pu s\'authentifier avec l\'instance. + L’application n’a pu s’authentifier avec l’instance. Impossible de trouver un navigateur web. - Une erreur d\'authorisation inconnu s\'est produite. + Une erreur d’autorisation inconnue s’est produite. Vous ne pouvez pas vous authentifier. - Impossible de récupérer le jeton d\'authentification. + Impossible de récupérer le jeton d’authentification. Votre pouet est trop long! Le fichier doit faire moins de 4Mo. - Ce type de fichier n\'est pas accepté. + Ce type de fichier n’est pas accepté. Le fichier ne peut être ouvert. - Une permision pour lire ce média est requis pour l\'uploader. + Une permission pour lire ce média est requis pour le mettre en ligne. Impossible de mettre une vidéo et une image sur le même pouet. - Ce média ne peut être uploadé. + Ce média ne peut être mis en ligne. Au moins un pouet a été reporté. Accueil Notifications Local Fédéré - Thread + Fil #%s Pouets - Follows - Followers + Abonnements + Abonnés Favoris Utilisateur bloqués @@ -40,24 +40,25 @@ fin des comptes %s à boosté votre pouet - %s à ajouter votre pouet en favoris + %s a ajouté votre pouet dans ses favoris %s vous suit Signaler @%s - Plus de commentaire ? + Davantage de commentaires ? - Écrire + Répondre Se connecter avec Mastodon Deconnexion - Follow - Unfollow + + Suivre + Ne plus suivre Bloquer Débloquer Signaler Supprimer POUET - POUET! - Ré-essayer + POUET ! + Essayer encore Définir le média comme sensible Masquer le texte par une mise en garde Ok @@ -76,52 +77,65 @@ Redonner la parole Mention NSFW + Ouvrir le menu + Plus + Répondre + Media + Favori + Option + Nettoyer + Boost - Partager l\'URL de votre pouet avec… + Partager l’URL de votre pouet avec… Rechercher un compte… Toot! - Envoyer! + Envoyer ! - Quelle instance? + Quelle instance ? Quoi de neuf ? - Contenu mis en garde + Contenu sensible - Qu\'est ce qu\'une instance? + Qu’est ce qu’une instance ? - L\'adresse ou le domaine d\'une instance peut être entré + L’adresse ou le domaine d’une instance peut être entré Ici, comme mastodon.social, icosahedron.website, social.tchncs.de, et plus (en anglais)! - \n\nSi vous n\'avez pas de compte, Vous pouvez entrer le nom de l\'instance que vous voulez rejoindre et créer un compte ici.\n\n Une instance est l\'endroit où votre compte est - stocké, mais vous pouvez facilement communiquer et suivre d\'autre personne sur d\'autre instance bien que vous soyez sur le même site - \n\nPlus d\'info mastodon.social (anglais). + \n\nSi vous n’avez pas de compte, Vous pouvez entrer le nom de l’instance que vous voulez rejoindre et créer un compte ici.\n\n Une instance est l’endroit où votre compte est + stocké, mais vous pouvez facilement communiquer et suivre d’autre personne sur d’autres instances bien que vous soyez sur le même site + \n\nPlus d’info mastodon.social (anglais). - Média uploadé avec succès - Téléversement… + Média mis en ligne avec succès + Mise en ligne… - Tout le monde peut voir - Tout le monde peut voir, mais cela ne sera pas listé sur votre timeline public - Uniquement les followers et les mentionnés peuvent voir + Afficher dans les fils publics + Ne pas afficher dans les fils publics + N’afficher que pour vos abonné⋅e⋅s Notifications + Modifier la notification Notifications push Alertes - Sonner pour notifier + Émettre un son pour notifier Vibrer pour notifier Notifier avec une LED Me notifier quand mentionné - suivit + suivi mes pouets boostés mes pouets mis en favoris Apparence Utiliser le thème clair + Navigateur + Utiliser le navigateur intégré + Cacher le bouton de suivi lors du défilement - %s vous ont mentionnés + %s vous ont mentionné⋅e %1$s, %2$s, %3$s et %4$d plus %1$s, %2$s, et %3$s %1$s et %2$s %d nouvelles interactions - + Compte bloqué + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000..902fb0a37 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,142 @@ + + + エラーが発生しました。 + 不正なドメイン名が入力されました + そのインスタンスでの認証が失敗しました + 使用するブラウザを見つけることができません。 + 未定義の認証エラーが発生しました。 + 認証は拒否されました。 + ログイントークンの取得に失敗しました。 + ステータスが非常に長い! + ファイルは4MB未満にしてください。 + このファイル種別はアップロードできません。 + このファイルは開けません。 + メディアの読み込み権限が必要です。 + 同一のステータスに画像とビデオを一緒にすることはできません。 + アップロードに失敗しました。 + 少なくとも1つのステータスを報告してください。 + + ホーム + 通知 + ローカル + 連合 + スレッド + #%s + 投稿 + フォロー + フォロワー + お気に入り + ブロックしたユーザー + + \@%s + %s 加速されました + 不適切なメディア + タップして表示 + さらに表示 + 省略表示 + + これ以降にステータスはありません + これ以降に通知はありません + これ以降にアカウントはありません + + %s があたなのトゥートをブーストしました + %s があなたのトゥートをお気に入りに追加しました + %s があなたをフォローしました + + \@%s を通報 + コメントを追加しますか? + + 返信 + ブースト + お気に入り + もっと読む + 作成 + Mastodonにログイン + ログアウト + フォロー + フォロー解除 + ブロック + ブロック解除 + 報告 + 削除 + トゥート + トゥート! + リトライ + 不適切なメディアとする + 警告のあるテキストを隠す + Ok + キャンセル + 閉じる + 戻る + プロファイル + 設定 + お気に入り + ブロックしたユーザー + スレッド + メディア + ブラウザで開く + 投稿 + メディアを追加 + 共有 + ミュート + ミュート解除 + メンション + NSFW + オプション + ドローワを開く + クリア + + トゥートURLを共通… + + アカウントを検索… + + トゥート! + 送信しました! + + どのインスタンス? + 今何してる? + コンテンツの警告 + + インスタンスって? + + インスタンスのアドレスもしくはドメインをここに入力してください。 + 例えば mastodon.social, mstdn.jp, pawoo.net, や + などです! + \n\nもしアカウントを持っていない場合は、参加したいインスタンスの名前を入力してください。\n\n + インスタンスはあなたのアカウントを単一の場所で管理(ホスト)しますが、ほかのインスタンスと同じサイトであるかのように人をフォローしたり連絡することができます。 + \n\n詳しくはmastodon.socialを見てください。 + + メディアのアップロードが完了 + アップロード中… + + すべての人が表示可能 + すべての人が表示可能だが公開タイムラインには載せない + フォロワーとメンションのみ表示可能 + + 通知 + 通知を編集 + プッシュ通知 + アラート + 音で通知 + バイブレーションで通知 + ライトで通知 + 通知する状態 + メンションがありました + フォローされた + 私の投稿がブーストされました + 私の投稿がお気に入りに追加されました + 表示 + 明るいテーマを使用 + ブラウザ + Chromeカスタムタブを使用 + スクロール中にフォローボタンを非表示 + + %s があなたにメンションを送信しました + %1$s、%2$s、%3$s と他に%4$d件 + %1$s、%2$s、と %3$s + %1$s と %2$s + %d 新しいお知らせ + + ロックされたアカウント + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 36f22fc39..c9185624d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ An error occurred. + This cannot be empty. Invalid domain entered Failed authenticating with that instance. Couldn\'t find a web browser to use. @@ -39,6 +40,7 @@ end of the statuses end of the notifications end of the accounts + There are no toots here so far. Pull down to refresh! %s boosted your toot %s favourited your toot @@ -89,7 +91,8 @@ Save Edit profile - Share toot URL to… + Share toot URL to… + Share toot to… Search accounts… @@ -148,5 +151,7 @@ %d new interactions Locked Account + Share content of toot + Share link to toot