Merge remote-tracking branch 'tuskyapp/master'

This commit is contained in:
kyori19 2019-10-03 21:09:01 +09:00
commit aa48555902
41 changed files with 812 additions and 247 deletions

View File

@ -113,7 +113,7 @@ dependencies {
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha05'
implementation 'com.google.android.material:material:1.1.0-alpha10'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.1.0-alpha04'

View File

@ -103,7 +103,6 @@
<activity android:name=".ViewTagActivity" />
<activity
android:name=".ViewMediaActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/TuskyBaseTheme" />
<activity
android:name=".AccountActivity"

View File

@ -227,20 +227,28 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
// Setup the toolbar.
setSupportActionBar(accountToolbar)
supportActionBar?.title = null
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
setDisplayShowTitleEnabled(false)
}
ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
@AttrRes
var priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
if(verticalOffset == oldOffset) {
return
}
oldOffset = verticalOffset
@AttrRes val attribute = if (titleVisibleHeight + verticalOffset < 0) {
supportActionBar?.setDisplayShowTitleEnabled(true)
@ -265,7 +273,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
accountFloatingActionButton.hide()
}
}
oldOffset = verticalOffset
val scaledAvatarSize = (avatarSize + verticalOffset) / avatarSize

View File

@ -50,12 +50,8 @@ import java.util.List;
import javax.inject.Inject;
import retrofit2.Call;
public abstract class BaseActivity extends AppCompatActivity implements Injectable {
protected List<Call> callList;
@Inject
public ThemeUtils themeUtils;
@Inject
@ -95,7 +91,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
redirectIfNotLoggedIn();
}
callList = new ArrayList<>();
requesters = new HashMap<>();
}
@ -164,14 +159,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
}
}
@Override
protected void onDestroy() {
for (Call call : callList) {
call.cancel();
}
super.onDestroy();
}
public void showAccountChooserDialog(CharSequence dialogTitle, boolean showActiveAccount, AccountSelectionListener listener) {
List<AccountEntity> accounts = accountManager.getAllAccountsOrderedByActive();
AccountEntity activeAccount = accountManager.getActiveAccount();

View File

@ -182,7 +182,7 @@ public final class ComposeActivity
ComposeAutoCompleteAdapter.AutocompletionProvider,
OnEmojiSelectedListener,
Injectable, InputConnectionCompat.OnCommitContentListener,
DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener {
TimePickerDialog.OnTimeSetListener {
private static final String TAG = "ComposeActivity"; // logging tag
static final int STATUS_CHARACTER_LIMIT = 500;
@ -244,8 +244,8 @@ public final class ComposeActivity
private ImageButton contentWarningButton;
private ImageButton emojiButton;
private ImageButton hideMediaToggle;
private TextView actionAddPoll;
private ImageButton scheduleButton;
private TextView actionAddPoll;
private Button atButton;
private Button hashButton;
@ -1867,6 +1867,7 @@ public final class ComposeActivity
color = ContextCompat.getColor(this, R.color.tusky_blue);
} else {
contentWarningBar.setVisibility(View.GONE);
textEditor.requestFocus();
color = ThemeUtils.getColor(this, android.R.attr.textColorTertiary);
}
contentWarningButton.getDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
@ -2098,6 +2099,10 @@ public final class ComposeActivity
updateVisibleCharactersLeft();
}
if (!new VersionUtils(instance.getVersion()).supportsScheduledToots()) {
scheduleButton.setVisibility(View.GONE);
}
if (instance.getPollLimits() != null) {
maxPollOptions = instance.getPollLimits().getMaxOptions();
maxPollOptionLength = instance.getPollLimits().getMaxOptionChars();
@ -2203,13 +2208,6 @@ public final class ComposeActivity
}
}
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
scheduleView.onDateSet(year, month, dayOfMonth);
updateScheduleButton();
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
scheduleView.onTimeSet(hourOfDay, minute);

View File

@ -8,7 +8,6 @@ import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.Gson
import com.keylesspalace.tusky.adapter.ScheduledTootAdapter
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
@ -44,8 +43,6 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootAdapter.ScheduledToot
@Inject
lateinit var eventHub: EventHub
val gson = Gson()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scheduled_toot)

View File

@ -812,7 +812,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
String description = context.getString(R.string.description_status,
status.getUserFullName(),
getContentWarningDescription(context, status),
(!status.isSensitive() || status.isExpanded() ? status.getContent() : ""),
(TextUtils.isEmpty(status.getSpoilerText()) || !status.isSensitive() || status.isExpanded() ? status.getContent() : ""),
getCreatedAtDescription(status.getCreatedAt()),
getReblogDescription(context, status),
status.getNickname(),

View File

@ -26,8 +26,8 @@ data class NewStatus(
val visibility: String,
val sensitive: Boolean,
@SerializedName("media_ids") val mediaIds: List<String>?,
val poll: NewPoll?,
@SerializedName("scheduled_at") val scheduledAt: String?,
val poll: NewPoll?,
@SerializedName("quote_id") val quoteId: String?
)

View File

@ -1,3 +1,18 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity
import com.google.gson.annotations.SerializedName

View File

@ -1,3 +1,18 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity
import com.google.gson.annotations.SerializedName

View File

@ -57,7 +57,8 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
lateinit var api: MastodonApi
private lateinit var type: Type
private lateinit var id: String
private var id: String? = null
private lateinit var scrollListener: EndlessOnScrollListener
private lateinit var adapter: AccountAdapter
private var fetching = false
@ -66,7 +67,7 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
type = arguments?.getSerializable(ARG_TYPE) as Type
id = arguments?.getString(ARG_ID)!!
id = arguments?.getString(ARG_ID)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -251,36 +252,52 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
Log.e(TAG, "Failed to $verb account id $accountId.")
}
private fun getFetchCallByListType(type: Type, fromId: String?): Single<Response<List<Account>>> {
private fun getFetchCallByListType(fromId: String?): Single<Response<List<Account>>> {
return when (type) {
Type.FOLLOWS -> api.accountFollowing(id, fromId)
Type.FOLLOWERS -> api.accountFollowers(id, fromId)
Type.FOLLOWS -> {
val accountId = requireId(type, id)
api.accountFollowing(accountId, fromId)
}
Type.FOLLOWERS -> {
val accountId = requireId(type, id)
api.accountFollowers(accountId, fromId)
}
Type.BLOCKS -> api.blocks(fromId)
Type.MUTES -> api.mutes(fromId)
Type.FOLLOW_REQUESTS -> api.followRequests(fromId)
Type.REBLOGGED -> api.statusRebloggedBy(id, fromId)
Type.FAVOURITED -> api.statusFavouritedBy(id, fromId)
Type.REBLOGGED -> {
val statusId = requireId(type, id)
api.statusRebloggedBy(statusId, fromId)
}
Type.FAVOURITED -> {
val statusId = requireId(type, id)
api.statusFavouritedBy(statusId, fromId)
}
}
}
private fun fetchAccounts(id: String? = null) {
private fun requireId(type: Type, id: String?): String {
return requireNotNull(id) { "id must not be null for type "+type.name }
}
private fun fetchAccounts(fromId: String? = null) {
if (fetching) {
return
}
fetching = true
if (id != null) {
if (fromId != null) {
recyclerView.post { adapter.setBottomLoading(true) }
}
getFetchCallByListType(type, id)
getFetchCallByListType(fromId)
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
.subscribe({ response ->
val accountList = response.body()
if (response.isSuccessful && accountList != null) {
val linkHeader = response.headers().get("Link")
val linkHeader = response.headers()["Link"]
onFetchAccountsSuccess(accountList, linkHeader)
} else {
onFetchAccountsFailure(Exception(response.message()))

View File

@ -1,40 +0,0 @@
package com.keylesspalace.tusky.fragment;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.keylesspalace.tusky.ComposeActivity;
import java.util.Calendar;
import java.util.TimeZone;
public class DatePickerFragment extends DialogFragment {
public static final String PICKER_TIME_YEAR = "picker_time_year";
public static final String PICKER_TIME_MONTH = "picker_time_month";
public static final String PICKER_TIME_DAY = "picker_time_day";
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
if (args != null) {
calendar.set(args.getInt(PICKER_TIME_YEAR),
args.getInt(PICKER_TIME_MONTH),
args.getInt(PICKER_TIME_DAY));
}
return new DatePickerDialog(getContext(),
android.R.style.Theme_DeviceDefault_Dialog,
(ComposeActivity) getActivity(),
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH));
}
}

View File

@ -33,6 +33,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityOptionsCompat;
@ -52,6 +53,7 @@ import com.keylesspalace.tusky.db.AccountManager;
import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Filter;
import com.keylesspalace.tusky.entity.PollOption;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases;
@ -474,7 +476,8 @@ public abstract class SFragment extends BaseFragment implements Injectable {
});
}
void reloadFilters(boolean forceRefresh) {
@VisibleForTesting
public void reloadFilters(boolean forceRefresh) {
if (filters != null && !forceRefresh) {
applyFilters(forceRefresh);
return;
@ -498,7 +501,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
});
}
protected boolean filterIsRelevant(Filter filter) {
protected boolean filterIsRelevant(@NonNull Filter filter) {
// Called when building local filter expression
// Override to select relevant filters for your fragment
return false;
@ -509,7 +512,17 @@ public abstract class SFragment extends BaseFragment implements Injectable {
// Override to refresh your fragment
}
boolean shouldFilterStatus(Status status) {
@VisibleForTesting
public boolean shouldFilterStatus(Status status) {
if(filterRemoveRegex && status.getPoll() != null) {
for(PollOption option: status.getPoll().getOptions()) {
if(filterRemoveRegexMatcher.reset(option.getTitle()).find()) {
return true;
}
}
}
return (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find()
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find())));
}

View File

@ -1,3 +1,18 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.fragment;
import android.app.Dialog;

View File

@ -76,7 +76,6 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
import net.accelf.yuito.TimelineStreamingListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
@ -356,12 +355,12 @@ public class TimelineFragment extends SFragment implements
}
private void updateCurrent() {
String topId;
if (this.statuses.isEmpty()) {
topId = null;
} else {
topId = CollectionsKt.first(this.statuses, Either::isRight).asRight().getId();
return;
}
String topId = CollectionsKt.first(this.statuses, Either::isRight).asRight().getId();
this.timelineRepo.getStatuses(topId, null, null, LOAD_AT_ONCE,
TimelineRequestMode.NETWORK)
.observeOn(AndroidSchedulers.mainThread())
@ -373,7 +372,7 @@ public class TimelineFragment extends SFragment implements
if (!statuses.isEmpty()) {
filterStatuses(statuses);
if (!this.statuses.isEmpty() && topId != null) {
if (!this.statuses.isEmpty()) {
// clear old cached statuses
Iterator<Either<Placeholder, Status>> iterator = this.statuses.iterator();
while (iterator.hasNext()) {
@ -449,7 +448,7 @@ public class TimelineFragment extends SFragment implements
}
@Override
protected boolean filterIsRelevant(Filter filter) {
protected boolean filterIsRelevant(@NonNull Filter filter) {
return filterContextMatchesKind(kind, filter.getContext());
}

View File

@ -691,7 +691,7 @@ public final class ViewThreadFragment extends SFragment implements
}
@Override
protected boolean filterIsRelevant(Filter filter) {
protected boolean filterIsRelevant(@NonNull Filter filter) {
return filter.getContext().contains(Filter.THREAD);
}

View File

@ -50,7 +50,7 @@ class ViewVideoFragment : ViewMediaFragment() {
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
// Start/pause/resume video playback as fragment is shown/hidden
super.setUserVisibleHint(isVisibleToUser)
if (videoPlayer == null) {
if (videoView == null) {
return
}
@ -58,10 +58,10 @@ class ViewVideoFragment : ViewMediaFragment() {
if (mediaActivity.isToolbarVisible) {
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
}
videoPlayer.start()
videoView.start()
} else {
handler.removeCallbacks(hideToolbar)
videoPlayer.pause()
videoView.pause()
mediaController.hide()
}
}
@ -69,18 +69,31 @@ class ViewVideoFragment : ViewMediaFragment() {
@SuppressLint("ClickableViewAccessibility")
override fun setupMediaView(url: String, previewUrl: String?) {
descriptionView = mediaDescription
val videoView = videoPlayer
videoView.transitionName = url
videoView.setVideoPath(url)
mediaController = MediaController(mediaActivity)
mediaController.setMediaPlayer(videoPlayer)
videoPlayer.setMediaController(mediaController)
mediaController.setMediaPlayer(videoView)
videoView.setMediaController(mediaController)
videoView.requestFocus()
videoView.setOnTouchListener { _, _ ->
mediaActivity.onPhotoTap()
false
}
videoView.setOnPreparedListener { mp ->
val containerWidth = videoContainer.measuredWidth.toFloat()
val containerHeight = videoContainer.measuredHeight.toFloat()
val videoWidth = mp.videoWidth.toFloat()
val videoHeight = mp.videoHeight.toFloat()
if(containerWidth/containerHeight > videoWidth/videoHeight) {
videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
} else {
videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
}
progressBar.hide()
mp.isLooping = true
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
@ -117,7 +130,7 @@ class ViewVideoFragment : ViewMediaFragment() {
}
override fun onToolbarVisibilityChange(visible: Boolean) {
if (videoPlayer == null || !userVisibleHint) {
if (videoView == null || !userVisibleHint) {
return
}

View File

@ -141,8 +141,8 @@ class SendTootService : Service(), Injectable {
tootToSend.visibility,
tootToSend.sensitive,
tootToSend.mediaIds,
tootToSend.poll,
tootToSend.scheduledAt,
tootToSend.poll,
tootToSend.quoteId
)

View File

@ -1,3 +1,18 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import java.util.regex.Matcher;
@ -21,7 +36,7 @@ public class VersionUtils {
}
public boolean supportsScheduledToots() {
return major >= 2 && minor >= 7 && patch >= 0;
return (major == 2) ? ( (minor == 7) ? (patch >= 0) : (minor > 7) ) : (major > 2);
}
}

View File

@ -1,3 +1,18 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view;
import android.content.Context;
@ -10,8 +25,10 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.fragment.DatePickerFragment;
import com.keylesspalace.tusky.fragment.TimePickerFragment;
import java.text.DateFormat;
@ -29,8 +46,7 @@ public class ComposeScheduleView extends ConstraintLayout {
private SimpleDateFormat iso8601;
private Button resetScheduleButton;
private TextView scheduledDateView;
private TextView scheduledTimeView;
private TextView scheduledDateTimeView;
private Calendar scheduleDateTime;
@ -58,11 +74,9 @@ public class ComposeScheduleView extends ConstraintLayout {
iso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
resetScheduleButton = findViewById(R.id.resetScheduleButton);
scheduledDateView = findViewById(R.id.scheduledDate);
scheduledTimeView = findViewById(R.id.scheduledTime);
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
scheduledDateView.setOnClickListener(v -> openPickDateDialog());
scheduledTimeView.setOnClickListener(v -> openPickTimeDialog());
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
scheduleDateTime = null;
@ -73,16 +87,16 @@ public class ComposeScheduleView extends ConstraintLayout {
private void setScheduledDateTime() {
if (scheduleDateTime == null) {
scheduledDateView.setText(R.string.hint_configure_scheduled_toot);
scheduledTimeView.setText(R.string.hint_configure_scheduled_toot);
scheduledDateTimeView.setText(R.string.hint_configure_scheduled_toot);
} else {
scheduledDateView.setText(dateFormat.format(scheduleDateTime.getTime()));
scheduledTimeView.setText(timeFormat.format(scheduleDateTime.getTime()));
scheduledDateTimeView.setText(String.format("%s %s",
dateFormat.format(scheduleDateTime.getTime()),
timeFormat.format(scheduleDateTime.getTime())));
}
}
private void setEditIcons() {
final int size = scheduledDateView.getLineHeight();
final int size = scheduledDateTimeView.getLineHeight();
Drawable icon = getContext().getDrawable(R.drawable.ic_create_24dp);
if (icon == null) {
@ -91,8 +105,7 @@ public class ComposeScheduleView extends ConstraintLayout {
icon.setBounds(0, 0, size, size);
scheduledDateView.setCompoundDrawables(null, null, icon, null);
scheduledTimeView.setCompoundDrawables(null, null, icon, null);
scheduledDateTimeView.setCompoundDrawables(null, null, icon, null);
}
public void setResetOnClickListener(OnClickListener listener) {
@ -105,16 +118,20 @@ public class ComposeScheduleView extends ConstraintLayout {
}
private void openPickDateDialog() {
DatePickerFragment picker = new DatePickerFragment();
if (scheduleDateTime != null) {
Bundle args = new Bundle();
args.putInt(DatePickerFragment.PICKER_TIME_YEAR, scheduleDateTime.get(Calendar.YEAR));
args.putInt(DatePickerFragment.PICKER_TIME_MONTH, scheduleDateTime.get(Calendar.MONTH));
args.putInt(DatePickerFragment.PICKER_TIME_DAY, scheduleDateTime.get(Calendar.DAY_OF_MONTH));
picker.setArguments(args);
long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000;
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
.setValidator(new DateValidatorPointForward(yesterday))
.build();
if (scheduleDateTime == null) {
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
}
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(),
"date_picker");
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
.datePicker()
.setSelection(scheduleDateTime.getTimeInMillis())
.setCalendarConstraints(calendarConstraints)
.build();
picker.addOnPositiveButtonClickListener(this::onDateSet);
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "date_picker");
}
private void openPickTimeDialog() {
@ -125,8 +142,7 @@ public class ComposeScheduleView extends ConstraintLayout {
args.putInt(TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE));
picker.setArguments(args);
}
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(),
"time_picker");
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
}
public void setDateTime(String scheduledAt) {
@ -143,12 +159,14 @@ public class ComposeScheduleView extends ConstraintLayout {
setScheduledDateTime();
}
public void onDateSet(int year, int month, int dayOfMonth) {
private void onDateSet(long selection) {
if (scheduleDateTime == null) {
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
}
scheduleDateTime.set(year, month, dayOfMonth);
setScheduledDateTime();
Calendar newDate = Calendar.getInstance(TimeZone.getDefault());
newDate.setTimeInMillis(selection);
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
openPickTimeDialog();
}
public void onTimeSet(int hourOfDay, int minute) {

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View File

@ -17,6 +17,7 @@
android:id="@+id/accountAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:elevation="@dimen/actionbar_elevation">
<com.google.android.material.appbar.CollapsingToolbarLayout
@ -232,7 +233,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="sans-serif-medium"
android:textColor="@color/account_tab_font_color"
android:textSize="?attr/status_text_medium"
@ -293,7 +293,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@android:color/transparent"
android:fontFamily="sans-serif-medium"
android:textColor="@color/account_tab_font_color"
android:textSize="?attr/status_text_medium"
@ -327,7 +326,6 @@
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
app:tabGravity="center"
app:tabMode="scrollable"

View File

@ -1,46 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center"
android:id="@+id/videoContainer"
android:clickable="true"
android:focusable="true">
<VideoView
android:id="@+id/videoPlayer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center"
/>
<TextView
android:id="@+id/mediaDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
android:background="#60000000"
android:lineSpacingMultiplier="1.1"
android:padding="8dp"
android:textAlignment="center"
android:textColor="#eee"
android:textSize="?attr/status_text_medium"
app:layout_constraintTop_toTopOf="parent"
tools:text="Some media description" />
<VideoView
android:id="@+id/videoView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center" />
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,33 +14,18 @@
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/scheduledDate"
android:id="@+id/scheduledDateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:paddingEnd="16dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
android:drawablePadding="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="2020/01/01" />
<TextView
android:id="@+id/scheduledTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
android:drawablePadding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/scheduledDate"
tools:text="00:00:00" />
tools:text="2020/01/01 00:00:00" />
</merge>

View File

@ -510,4 +510,14 @@
<string name="poll_duration_6_hours">6 hores</string>
<string name="edit_poll">Modificar</string>
</resources>
<string name="action_add_poll">Afegeix una enquesta</string>
<string name="create_poll_title">Enquesta</string>
<string name="poll_duration_5_min">5 minuts</string>
<string name="poll_duration_30_min">30 minuts</string>
<string name="poll_duration_1_day">1 dia</string>
<string name="poll_duration_3_days">3 dies</string>
<string name="poll_duration_7_days">7 dies</string>
<string name="add_poll_choice">Afegeix una tria</string>
<string name="poll_allow_multiple_choices">Múltiples tries</string>
<string name="poll_new_choice_hint">Tria %d</string>
</resources>

View File

@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="error_generic">خطایی رخ داد</string>
<string name="error_generic">خطایی رخ داد.</string>
<string name="error_empty">این نمی‌تواند خالی باشد.</string>
<string name="error_invalid_domain">دامنه وارد شده نامعتبر است</string>
<string name="error_failed_app_registration">احراز هویت با سرور ناموفق بود</string>
<string name="error_no_web_browser_found">مرورگری برای استفاده یافت نشد</string>
<string name="error_failed_app_registration">احراز هویت با این نمونه ناموفق بود.</string>
<string name="error_no_web_browser_found">مرورگری برای استفاده یافت نشد.</string>
<string name="error_authorization_unknown">خطای احراز هویت ناشناس رخ داده است.</string>
<string name="error_authorization_denied">هویت احراز نشد</string>
<string name="error_retrieving_oauth_token">دریافت توکن ورود ناموفق بود</string>
<string name="error_authorization_denied">هویت احراز نشد.</string>
<string name="error_retrieving_oauth_token">دریافت توکن ورود ناموفق بود.</string>
<string name="error_compose_character_limit">وضعیت خیلی طولانی است!</string>
<string name="error_image_upload_size">پرونده باید کمتر از ۸ مگابایت باشد</string>
<string name="error_video_upload_size">پرونده ویدئویی باید کمتر از ۴۰ مگابایت باشد</string>
<string name="error_media_upload_type">این نوع از پرونده نمی‌تواند بارگذاری شود</string>
<string name="error_media_upload_opening">پرونده باز نشد</string>
<string name="error_media_upload_permission">مجوز برای خواندن رسانه نیاز است</string>
<string name="error_media_download_permission">اجازه ذخیره رسانه نیاز است</string>
<string name="error_image_upload_size">پرونده باید کمتر از ۸ مگابایت باشد.</string>
<string name="error_video_upload_size">پرونده‌های ویدئویی باید کمتر از ۴۰ مگابایت باشد.</string>
<string name="error_media_upload_type">این نوع از پرونده نمی‌تواند بارگذاری شود.</string>
<string name="error_media_upload_opening">پرونده باز نشد.</string>
<string name="error_media_upload_permission">مجوز برای خواندن رسانه نیاز است.</string>
<string name="error_media_download_permission">اجازه ذخیره رسانه نیاز است.</string>
<string name="error_media_upload_image_or_video">تصاویر و فیلم‌ها هر دو نمی‌توانند به یک وضعیت ضمیمه شوند.</string>
<string name="error_media_upload_sending">بارگذاری ناموفق بود</string>
<string name="error_sender_account_gone">خطا در ارسال بوق</string>
<string name="error_media_upload_sending">بارگذاری ناموفق بود.</string>
<string name="error_sender_account_gone">خطا در ارسال بوق.</string>
<string name="title_home">خانه</string>
<string name="title_notifications">اعلان‌ها</string>
<string name="title_public_local">محلی</string>
@ -25,9 +25,9 @@
<string name="title_view_thread">بوق</string>
<string name="title_statuses">پست‌ها</string>
<string name="title_statuses_with_replies">با پاسخ‌</string>
<string name="title_follows">دنبال شونده‌ها</string>
<string name="title_followers">دنبال کننده‌ها</string>
<string name="title_favourites">علاقه‌مندی‌ها</string>
<string name="title_follows">دنبالشونده‌ها</string>
<string name="title_followers">دنبالکننده‌ها</string>
<string name="title_favourites">پسندها</string>
<string name="title_mutes">کاربرهای بی‌صدا</string>
<string name="title_blocks">کاربرهای مسدود شده</string>
<string name="title_follow_requests">درخواست‌های دنبال کردن</string>
@ -40,21 +40,21 @@
<string name="status_sensitive_media_directions">برای نمایش کلیک کن</string>
<string name="status_content_warning_show_more">نمایش بیشتر</string>
<string name="status_content_warning_show_less">نمایش کمتر</string>
<string name="status_content_show_more">بازکردن</string>
<string name="status_content_show_more">گسترش دادن</string>
<string name="status_content_show_less">بستن</string>
<string name="footer_empty">چیزی اینجا نیست.برای تازه سازی، به پایین بکشید</string>
<string name="footer_empty">چیزی اینجا نیست. برای تازه‌سازی، به پایین بکشید!</string>
<string name="notification_reblog_format">%s بوق شما را تقویت کرد</string>
<string name="notification_favourite_format">%s بوق شما را پسندید</string>
<string name="notification_follow_format">%s شما را دنبال می‌کند</string>
<string name="report_username_format">گزارش @%s</string>
<string name="report_comment_hint">پیام‌های اضافی</string>
<string name="report_comment_hint">پیام‌های اضافی؟</string>
<string name="action_quick_reply">پاسخ سریع</string>
<string name="action_reply">پاسخ</string>
<string name="action_reblog">تقویت</string>
<string name="action_favourite">مورد علاقه</string>
<string name="action_favourite">پسند</string>
<string name="action_more">بیشتر</string>
<string name="action_compose">ایجاد</string>
<string name="action_login">با mastodon وارد شو</string>
<string name="action_login">با ماستودون وارد شو</string>
<string name="action_logout">خروج</string>
<string name="action_logout_confirm">آیا از خارج شدن از این حساب %1s اطمینان دارید؟</string>
<string name="action_follow">دنبال کن</string>
@ -68,11 +68,11 @@
<string name="action_send">بوق</string>
<string name="action_send_public">بوق!</string>
<string name="action_retry">تلاش مجدد</string>
<string name="action_close">بستن</string>
<string name="action_close">بببند</string>
<string name="action_view_profile">نمایه</string>
<string name="action_view_preferences">ترجیحات</string>
<string name="action_view_account_preferences">ترجیحات حساب</string>
<string name="action_view_favourites">علاقه‌مندی‌ها</string>
<string name="action_view_favourites">پسندها</string>
<string name="action_view_mutes">کاربران بی‌صدا</string>
<string name="action_view_blocks">کاربران مسدود شده</string>
<string name="action_view_follow_requests">درخواست‌های دنبال کردن</string>
@ -89,18 +89,18 @@
<string name="action_save">ذخیره</string>
<string name="action_edit_profile">ویرایش نمایه</string>
<string name="action_edit_own_profile">ویرایش</string>
<string name="action_undo">بازگشت</string>
<string name="action_undo">بازگرداندن</string>
<string name="action_accept">پذیرش</string>
<string name="action_reject">رد</string>
<string name="action_search">جستجو</string>
<string name="action_access_saved_toot">پیش‌نویس</string>
<string name="action_toggle_visibility">نمایش بوق</string>
<string name="action_content_warning">هشدار محتوی</string>
<string name="action_content_warning">هشدار محتوا</string>
<string name="action_emoji_keyboard">صفحه کلیک شکلک</string>
<string name="download_image">درحال دریافت %1$s</string>
<string name="action_copy_link">کپی لینک</string>
<string name="send_status_link_to">هم‌رسانی نشانی بوق با</string>
<string name="send_status_content_to">هم‌رسانی بوق با</string>
<string name="action_copy_link">رونوشت پیوند</string>
<string name="send_status_link_to">هم‌رسانی نشانی بوق با</string>
<string name="send_status_content_to">هم‌رسانی بوق با</string>
<string name="send_media_to">هم‌رسانی رسانه با…</string>
<string name="confirmation_reported">فرستاده شد!</string>
<string name="confirmation_unblocked">کاربر رفع انسداد شد</string>
@ -109,23 +109,29 @@
<string name="status_sent_long">پاسخ با موفق فرستاده شد‌.</string>
<string name="hint_domain">کدام نمونه؟</string>
<string name="hint_compose">چه خبر؟</string>
<string name="hint_content_warning">هشدار محتوی</string>
<string name="hint_content_warning">هشدار محتوا</string>
<string name="hint_display_name">نام نمایشی</string>
<string name="hint_note">بیوگرافی</string>
<string name="hint_search">جستجو</string>
<string name="hint_search">جستجو</string>
<string name="search_no_results">نتیجه‌ای نیست</string>
<string name="label_quick_reply">پاسخ …</string>
<string name="label_avatar">آواتار</string>
<string name="label_header">سرتیتر</string>
<string name="label_header">سرایند</string>
<string name="link_whats_an_instance">یک نمونه چیست؟</string>
<string name="login_connection">در حال اتصال …</string>
<string name="dialog_whats_an_instance">آدرس یا دامنه هر نمونه را می‌توانید وارد کنید، مثلا mastodon.social, icosahedron.website, social.tchncs.de, و &lt;a href=\"https://instances.social\" &gt;بیشتر!\n\n اگر شما هنوز حساب کاربری ندارید، می‌توانید نام نمونه مورد نظر را وارد کنید از اینجا بپیوندید و حساب کاربری ایجاد کنید\n\n نمونه جایی است که حساب کاربری شما میزبان آن است اما شما به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کنید و آنها را دنبال کنید شما درست مثل اینکه در یکجا باشید.\n\n برای اطلاعات بیشتر به اینجا مراجعه کنید <a href="https://joinmastodon.org">joinmastodon.org</a>.</string>
<string name="dialog_whats_an_instance">آدرس یا دامنه هر نمونه را می‌توانید وارد کنید، مثلا mastodon.social، icosahedron.website، social.tchncs.de، و <a href="https://instances.social">بیشتر!</a>
\n
\n اگر شما هنوز حساب کاربری ندارید، می‌توانید نام نمونه مورد نظر را وارد کنید از اینجا بپیوندید و حساب کاربری ایجاد کنید.
\n
\n نمونه جایی است که حساب کاربری شما میزبان آن است اما شما به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کنید و آنها را دنبال کنید شما درست مثل اینکه در یکجا باشید.
\n
\n برای اطلاعات بیشتر به اینجا مراجعه کنید <a href="https://joinmastodon.org">joinmastodon.org</a>. </string>
<string name="dialog_title_finishing_media_upload">پایان بارگذاری رسانه</string>
<string name="dialog_message_uploading_media">در حال بارگذاری</string>
<string name="dialog_message_uploading_media">در حال بارگذاری</string>
<string name="dialog_download_image">بارگیری</string>
<string name="dialog_message_cancel_follow_request">درخواست دنبال کردن را لغو می‌کنید؟</string>
<string name="dialog_unfollow_warning">لغو دنبال کردن این حساب</string>
<string name="dialog_delete_toot_warning">حذف این بوق</string>
<string name="dialog_unfollow_warning">لغو دنبال کردن این حساب؟</string>
<string name="dialog_delete_toot_warning">حذف این بوق؟</string>
<string name="visibility_public">عمومی:پست به خط زمانی عمومی</string>
<string name="visibility_unlisted">خارج از لیست: در خط زمانی عمومی نشان نده</string>
<string name="visibility_private">تنها دنبال‌کنندگان:پست فقط به دنبال‌کنندگان</string>
@ -140,13 +146,13 @@
<string name="pref_title_notification_filter_mentions">صدازده‌ها</string>
<string name="pref_title_notification_filter_follows">دنبال‌شده</string>
<string name="pref_title_notification_filter_reblogs">پست‌های تقویت شده من</string>
<string name="pref_title_notification_filter_favourites">پست‌های مورد علاقه من</string>
<string name="pref_title_notification_filter_favourites">پست‌های پسندیده شدهٔ من</string>
<string name="pref_title_appearance_settings">ظاهر</string>
<string name="pref_title_app_theme">تم برنامه</string>
<string name="pref_title_timelines">خط‌های زمانی</string>
<string name="app_them_dark">روشن</string>
<string name="app_theme_light">سیاه</string>
<string name="app_theme_black">تیره</string>
<string name="app_them_dark">تاریک</string>
<string name="app_theme_light">روشن</string>
<string name="app_theme_black">سیاه</string>
<string name="app_theme_auto">خودکار برحسب غروب خورشید</string>
<string name="pref_title_browser_settings">مرورگر</string>
<string name="pref_title_custom_tabs">استفاده از زبانه‌های سفارشی کروم</string>
@ -163,25 +169,25 @@
<string name="pref_title_http_proxy_port">درگاه پراکسی HTTP</string>
<string name="pref_default_post_privacy">حریم خصوصی پیش‌فرض پست</string>
<string name="pref_default_media_sensitivity">همواره رسانه را به عنوان حساس نشانه‌گذاری کن</string>
<string name="pref_publishing">انتشار</string>
<string name="pref_publishing">انتشار (همگادم با کارساز)</string>
<string name="pref_failed_to_sync">ناتوانی در هم‌گام‌سازی تنظیمات</string>
<string name="post_privacy_public">عمومی</string>
<string name="post_privacy_unlisted">لیست‌نشده</string>
<string name="post_privacy_unlisted">فهرست نشده</string>
<string name="post_privacy_followers_only">فقط دنبال‌کنندگان</string>
<string name="pref_status_text_size">اندازه متن وضعیت</string>
<string name="status_text_size_smallest">کوچک‌ترین</string>
<string name="status_text_size_smallest">بسیار کوچک</string>
<string name="status_text_size_small">کوچک</string>
<string name="status_text_size_medium">متوسط</string>
<string name="status_text_size_large">بزرگ</string>
<string name="status_text_size_largest">بزرگ‌ترین</string>
<string name="notification_mention_name">صدا زدن جدید</string>
<string name="notification_mention_descriptions">هشدار در مورد صدازدن جدید</string>
<string name="status_text_size_largest">بسیار بزرگ</string>
<string name="notification_mention_name">اشاره‌های جدید</string>
<string name="notification_mention_descriptions">اعلان در مورد اشاره‌های جدید</string>
<string name="notification_follow_name">دنبال‌کننده‌های جدید</string>
<string name="notification_follow_description">هشدارها درمورد دنبال‌کنندگان جدید</string>
<string name="notification_boost_name">تقویت</string>
<string name="notification_boost_description">وقتی بوق‌های شما تقویت شد هشدار بده</string>
<string name="notification_favourite_name">مورد علاقه‌ها</string>
<string name="notification_favourite_description">هشدارها، زمانی که بوق‌های شما مورد علاقه قرار گرفت</string>
<string name="notification_follow_description">اعلان درمورد دنبال‌کنندگان جدید</string>
<string name="notification_boost_name">تقویتها</string>
<string name="notification_boost_description">وقتی بوق‌های شما تقویت شد اعلان بده</string>
<string name="notification_favourite_name">پسندها</string>
<string name="notification_favourite_description">اعلان‌ها وقتی که بوق‌های شما پسندیده شوند</string>
<string name="notification_mention_format">%s شما را صدا زد</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s و %4$d دیگر</string>
<string name="notification_summary_medium">%1$s, %2$s, و %3$s</string>
@ -212,20 +218,20 @@
<string name="state_follow_requested">‌درخواست دنبال‌کردن فرستاده شد</string>
<string name="no_content">بدون محتوی</string>
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
<string name="abbreviated_in_years">در %dy</string>
<string name="abbreviated_in_days">در %dd</string>
<string name="abbreviated_in_hours">در % dh</string>
<string name="abbreviated_in_minutes">در %dm</string>
<string name="abbreviated_in_seconds">در %ds</string>
<string name="abbreviated_in_years">در %dسال</string>
<string name="abbreviated_in_days">در %dر</string>
<string name="abbreviated_in_hours">در % dس</string>
<string name="abbreviated_in_minutes">در %dد</string>
<string name="abbreviated_in_seconds">در %dث</string>
<string name="follows_you">شما را دنبال می‌کند</string>
<string name="pref_title_alway_show_sensitive_media">همیشه مطالب حساس را نشان بده</string>
<string name="pref_title_alway_show_sensitive_media">همیشه مطلب حساس را نشان بده</string>
<string name="title_media">رسانه</string>
<string name="replying_to">پاسخ دادن به @%s</string>
<string name="load_more_placeholder_text">بارگیری بیشتر</string>
<string name="add_account_name">افزودن حساب</string>
<string name="add_account_description">افزودن حساب ماستدون جدید</string>
<string name="action_lists">لیستها</string>
<string name="title_lists">لیستها</string>
<string name="action_lists">سیاههها</string>
<string name="title_lists">سیاههها</string>
<string name="title_list_timeline">لیست خط زمانی</string>
<string name="compose_active_account_description">پست با حساب %1$s</string>
<string name="error_failed_set_caption">ناتوان در تنظیم عنوان</string>
@ -234,8 +240,8 @@
<string name="action_remove">حذف</string>
<string name="lock_account_label">قفل حساب</string>
<string name="lock_account_label_description">به شما امکان می‌دهد بصورت دستی دنبال‌کنندگان را تایید کنید</string>
<string name="compose_save_draft">ذخیره به عنوان پیش‌نویس</string>
<string name="send_toot_notification_title">فرستادن بوق</string>
<string name="compose_save_draft">ذخیره به عنوان پیش‌نویس؟</string>
<string name="send_toot_notification_title">فرستادن بوق</string>
<string name="send_toot_notification_error_title">خطای فرستادن بوق</string>
<string name="send_toot_notification_channel_name">در حال فرستادن بوق‌ها</string>
<string name="send_toot_notification_cancel_title">فرستادن لغو شد</string>
@ -246,8 +252,8 @@
<string name="emoji_style">قالب شکلک</string>
<string name="system_default">پیشفرض سیستم</string>
<string name="download_fonts">شما ابتدا باید این شکلک‌ها را دریافت کنید</string>
<string name="performing_lookup_title">اجرای جستجو</string>
<string name="expand_collapse_all_statuses">باز/بستن همه وضعیت‌ها</string>
<string name="performing_lookup_title">اجرای جستجو</string>
<string name="expand_collapse_all_statuses">گستردن/بستن همه وضعیت‌ها</string>
<string name="action_open_toot">بوق را باز کن</string>
<string name="restart_required">برنامه به شروع مجدد نیاز دارد</string>
<string name="restart_emoji">شما برای اعمال این تغییرات به شروع مجدد برنامه نیاز دارید</string>
@ -271,4 +277,186 @@
<string name="label_remote_account">اطلاعات زیر ممکن است به طور ناقص نمایه کاربر را نشان دهد. برای دیدن نمایه کامل در مرورگر، لمس کنید.</string>
<string name="unpin_action">برداشتن سنجاق</string>
<string name="pin_action">سنجاق کردن</string>
<string name="error_network">یک خطای شبکه رخ داد! لطفا اتصال خود را بررسی و دوباره تلاش کنید!</string>
<string name="title_direct_messages">پیام‌های مستقیم</string>
<string name="title_tab_preferences">زبانه‌ها</string>
<string name="title_tag">#%ث</string>
<string name="title_statuses_pinned">سنجاق‌شده‌ها</string>
<string name="title_domain_mutes">دامنه‌های پنهان</string>
<string name="status_username_format">\@%s</string>
<string name="message_empty">چیزی اینجا نیست.</string>
<string name="action_unreblog">حذف بازبوق</string>
<string name="action_unfavourite">حذف پسند</string>
<string name="action_delete_and_redraft">پاک کردن و بازنویسی</string>
<string name="action_view_domain_mutes">دامنه‌های پنهان</string>
<string name="action_add_poll">افزودن نظرسنجی</string>
<string name="action_mute_domain">بی‌صدا کردن %s</string>
<string name="action_add_tab">افزوده زبانه</string>
<string name="action_links">پیوندها</string>
<string name="action_mentions">اشاره‌ها</string>
<string name="action_hashtags">هشتگ‌ها</string>
<string name="action_open_reblogger">بازکردن حساب بازبوق کننده</string>
<string name="action_open_reblogged_by">نمایش بازبوق‌ها</string>
<string name="action_open_faved_by">نمایش پسندیده‌ها</string>
<string name="title_hashtags_dialog">هشتگ‌ها</string>
<string name="title_mentions_dialog">اشاره‌ها</string>
<string name="title_links_dialog">پیوندها</string>
<string name="action_open_media_n">بازکردن رسانه #%d</string>
<string name="action_open_as">باز کردن به عنوان %s</string>
<string name="action_share_as">هم‌رسانی به عنوان …</string>
<string name="download_media">بارگیری رسانه</string>
<string name="downloading_media">در حال بارگیری رسانه</string>
<string name="confirmation_domain_unmuted">%s ناپنهان</string>
<string name="dialog_redraft_toot_warning">می‌خواهید این بوق را پاک و بازنویسی کنید؟</string>
<string name="mute_domain_warning_dialog_ok">پنهان کردن تمام دامنه</string>
<string name="pref_title_notification_filter_poll">نظرسنجی‌های پایان یافته</string>
<string name="pref_title_timeline_filters">صافی‌ها</string>
<string name="app_theme_system">استفاده از طرح سیستم</string>
<string name="pref_title_language">زبان</string>
<string name="pref_title_bot_overlay">نمایش نشان برای بات‌ها</string>
<string name="pref_title_animate_gif_avatars">پویانمایی آواتار gif</string>
<string name="notification_poll_name">نظرسنجی‌ها</string>
<string name="notification_poll_description">اعلان‌ها درباره نظرسنجی‌هایی که پایان یافته‌اند</string>
<string name="about_tusky_version">Tusky (تاسکی) %s</string>
<string name="abbreviated_years_ago">%dسال</string>
<string name="abbreviated_days_ago">%dر</string>
<string name="abbreviated_hours_ago">%dس</string>
<string name="abbreviated_minutes_ago">%dد</string>
<string name="abbreviated_seconds_ago">%dث</string>
<string name="pref_title_alway_open_spoiler">همواره بوق‌هایی که دارای محتوای حساس هستند را گسترش بده</string>
<string name="pref_title_public_filter_keywords">خط زمانی عمومی</string>
<string name="pref_title_thread_filter_keywords">گفتگوها</string>
<string name="filter_addition_dialog_title">افزودن صافی</string>
<string name="filter_edit_dialog_title">ویرایش صافی</string>
<string name="filter_dialog_remove_button">پاک کردن</string>
<string name="filter_dialog_update_button">به‌روزرسانی</string>
<string name="filter_dialog_whole_word">تمام کلمه</string>
<string name="error_create_list">ناتوانی در ایجاد سیاهه</string>
<string name="error_rename_list">ناتوانی در تغییر نام سیاهه</string>
<string name="error_delete_list">ناتوانی در حذف سیاهه</string>
<string name="action_create_list">ایجاد یک سیاهه</string>
<string name="action_rename_list">تغییر نام سیاهه</string>
<string name="action_delete_list">حذف سیاهه</string>
<string name="action_edit_list">ویرایش سیاهه</string>
<string name="hint_search_people_list">جستجو بین افرادی که دنبال می‌کنید</string>
<string name="action_add_to_list">افزودن حساب به سیاهه</string>
<string name="action_remove_from_list">حذف حساب از سیاهه</string>
<string name="caption_notoemoji">مجموعه شکلک‌های جاری گوگل</string>
<string name="license_cc_by_4">CC-BY 4.0</string>
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
<plurals name="favs">
<item quantity="one"><b>%1$s</b> پسند</item>
<item quantity="other"><b>%1$s</b> پسند</item>
</plurals>
<plurals name="reblogs">
<item quantity="one"><b>%s</b> بازبوق</item>
<item quantity="other"><b>%s</b> بازبوق</item>
</plurals>
<string name="title_reblogged_by">بازبوق شده توسط</string>
<string name="title_favourited_by">پسننده‌شده توسط</string>
<string name="conversation_1_recipients">%1$s</string>
<string name="conversation_2_recipients">%1$s و %2$s</string>
<string name="conversation_more_recipients">%1$s، %2$s و %3$d بیشتر</string>
<string name="max_tab_number_reached">به بیشینه %1$d زبانه رسید</string>
<string name="description_status_media">رسانه: %s</string>
<string name="description_status_cw">هشدار محتوا: %s</string>
<string name="description_status_media_no_description_placeholder">بدون توضیحات</string>
<string name="description_status_reblogged">بازبوقیده</string>
<string name="description_status_favourited">پسندیده</string>
<string name="description_visiblity_public">عمومی</string>
<string name="description_visiblity_unlisted">فهرست‌نشده</string>
<string name="description_visiblity_private">دنبال‌کنندگان</string>
<string name="description_visiblity_direct">مستقیم</string>
<string name="description_poll">نظرسنجی با انتخاب‌ها: %1$s، %2$s، %3$s، %4$s؛ %5$s</string>
<string name="hint_list_name">نام سیاهه</string>
<string name="edit_hashtag_title">ویرایش هشتگ</string>
<string name="edit_hashtag_hint">هشتگ بدون #</string>
<string name="hashtag">هشتگ</string>
<string name="notifications_clear">پاک کردن</string>
<string name="notifications_apply_filter">صافی</string>
<string name="filter_apply">اعمال</string>
<string name="compose_shortcut_long_label">ایجاد بوق</string>
<string name="compose_shortcut_short_label">ایجاد</string>
<string name="notification_clear_text">مطمئنید که به طور دائمی می‌خواهید اعلان‌ها را پاک کنید؟</string>
<string name="compose_preview_image_description">کنش‌ها برای تصاویر %s</string>
<plurals name="poll_info_votes">
<item quantity="one">%s رای</item>
<item quantity="other">%s رای</item>
</plurals>
<string name="poll_info_time_relative">%s باقی مانده</string>
<string name="poll_info_time_absolute">پایان یافته در %s</string>
<string name="poll_info_closed">بسته شده</string>
<string name="poll_vote">رای</string>
<string name="poll_ended_voted">یک نظرسنجی که در آن رای داده‌اید پایان یافته است</string>
<string name="poll_ended_created">یک نظرسنجی ساخته‌اید پایان یافته است</string>
<plurals name="poll_timespan_days">
<item quantity="one">%d روز</item>
<item quantity="other">%d روز</item>
</plurals>
<plurals name="poll_timespan_hours">
<item quantity="one">%d ساعت</item>
<item quantity="other">%d ساعت</item>
</plurals>
<plurals name="poll_timespan_minutes">
<item quantity="one">%d دقیقه</item>
<item quantity="other">%d دقیقه</item>
</plurals>
<plurals name="poll_timespan_seconds">
<item quantity="one">%d ثانیه</item>
<item quantity="other">%d ثانیه</item>
</plurals>
<string name="button_continue">ادامه</string>
<string name="button_back">قبل</string>
<string name="button_done">اتمام</string>
<string name="report_sent_success">با موفقیت گزارش شد @%s</string>
<string name="hint_additional_info">نظرات بیشتر</string>
<string name="report_remote_instance">هدایت به %s</string>
<string name="failed_report">ناتوانی در گزارش</string>
<string name="failed_fetch_statuses">ناتوانی در دریافت وضعیت‌ها</string>
<string name="title_accounts">حساب‌ها</string>
<string name="failed_search">ناتوانی در جستجو</string>
<string name="pref_title_show_notifications_filter">نمایش فیلتر اعلانات</string>
<string name="create_poll_title">نظرسنجی</string>
<string name="poll_duration_5_min">۵ دقیقه</string>
<string name="poll_duration_30_min">۳۰ دقیقه</string>
<string name="poll_duration_1_hour">۱ ساعت</string>
<string name="poll_duration_6_hours">۶ ساعت</string>
<string name="poll_duration_1_day">۱ روز</string>
<string name="poll_duration_3_days">۳ روز</string>
<string name="poll_duration_7_days">۷ روز</string>
<string name="add_poll_choice">افزودن گزینه</string>
<string name="poll_allow_multiple_choices">گزینه‌های چندگانه</string>
<string name="poll_new_choice_hint">گزینه %d</string>
<string name="edit_poll">ویرایش</string>
</resources>

View File

@ -50,7 +50,7 @@
<string name="status_content_show_less">Összecsukás</string>
<string name="message_empty">Nincs itt semmi.</string>
<string name="footer_empty">Üres tartalom. Húzd le a frissítéshez!</string>
<string name="notification_reblog_format">%s megolta a tülködet</string>
<string name="notification_reblog_format">%s megtolta a tülködet</string>
<string name="notification_favourite_format">%s kedvencnek jelölte tülködet</string>
<string name="notification_follow_format">%s követ</string>
<string name="report_username_format">\@%s bejelentése</string>
@ -453,4 +453,18 @@
<string name="title_accounts">Fiókok</string>
<string name="failed_search">Sikertelen keresés</string>
</resources>
<string name="action_add_poll">Szavazás hozzáadása</string>
<string name="create_poll_title">Szavazás</string>
<string name="poll_duration_5_min">5 perc</string>
<string name="poll_duration_30_min">30 perc</string>
<string name="poll_duration_1_hour">1 óra</string>
<string name="poll_duration_6_hours">6 óra</string>
<string name="poll_duration_1_day">1 nap</string>
<string name="poll_duration_3_days">3 nap</string>
<string name="poll_duration_7_days">7 nap</string>
<string name="add_poll_choice">Válasz hozzáadása</string>
<string name="poll_allow_multiple_choices">Több lehetőség</string>
<string name="poll_new_choice_hint">Válasz %d</string>
<string name="edit_poll">Szerkesztés</string>
</resources>

View File

@ -125,6 +125,8 @@
<string name="action_toggle_visibility">Toot visibility</string>
<string name="action_content_warning">Content warning</string>
<string name="action_emoji_keyboard">Emoji keyboard</string>
<string name="action_schedule_toot">Schedule Toot</string>
<string name="action_reset_schedule">Reset</string>
<string name="action_add_tab">Add Tab</string>
<string name="action_links">Links</string>
<string name="action_mentions">Mentions</string>
@ -134,8 +136,6 @@
<string name="action_open_faved_by">Show favorites</string>
<string name="action_quote">Quote</string>
<string name="action_authorize">Authorize Now!</string>
<string name="action_schedule_toot">Schedule Toot</string>
<string name="action_reset_schedule">Reset</string>
<string name="title_hashtags_dialog">Hashtags</string>
<string name="title_mentions_dialog">Mentions</string>
@ -164,6 +164,7 @@
<string name="hint_domain">Which instance?</string>
<string name="hint_compose">What\'s happening?</string>
<string name="hint_configure_scheduled_toot">Tap here to configure scheduled toot.</string>
<string name="hint_content_warning">Content warning</string>
<string name="hint_display_name">Display name</string>
<string name="hint_note">Bio</string>
@ -171,7 +172,6 @@
<string name="hint_toot_area">Quick Toot Area</string>
<string name="hint_default_text">Default Hashtag</string>
<string name="hint_access_token">Access Token</string>
<string name="hint_configure_scheduled_toot">Tap here to configure scheduled toot.</string>
<string name="search_no_results">No results</string>

View File

@ -268,7 +268,6 @@ class BottomSheetActivityTest {
mastodonApi = api
@Suppress("UNCHECKED_CAST")
bottomSheet = mock(BottomSheetBehavior::class.java) as BottomSheetBehavior<LinearLayout>
callList = arrayListOf()
}
override fun openLink(url: String) {

View File

@ -0,0 +1,238 @@
package com.keylesspalace.tusky
import android.os.Bundle
import android.text.SpannedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.PollOption
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.network.MastodonApi
import com.nhaarman.mockitokotlin2.mock
import okhttp3.Request
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.robolectric.Robolectric
import org.robolectric.annotation.Config
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
@Config(application = FakeTuskyApplication::class)
@RunWith(AndroidJUnit4::class)
class FilterTest {
private val fragment = FakeFragment()
@Before
fun setup() {
val controller = Robolectric.buildActivity(FakeActivity::class.java)
val activity = controller.get()
activity.accountManager = mock()
activity.themeUtils = mock()
val apiMock = Mockito.mock(MastodonApi::class.java)
Mockito.`when`(apiMock.getFilters()).thenReturn(object: Call<List<Filter>> {
override fun isExecuted(): Boolean {
return false
}
override fun clone(): Call<List<Filter>> {
throw Error("not implemented")
}
override fun isCanceled(): Boolean {
throw Error("not implemented")
}
override fun cancel() {
throw Error("not implemented")
}
override fun execute(): Response<List<Filter>> {
throw Error("not implemented")
}
override fun request(): Request {
throw Error("not implemented")
}
override fun enqueue(callback: Callback<List<Filter>>) {
callback.onResponse(
this,
Response.success(
listOf(
Filter(
id = "123",
phrase = "badWord",
context = listOf(Filter.HOME),
expiresAt = null,
irreversible = false,
wholeWord = false
),
Filter(
id = "123",
phrase = "badWholeWord",
context = listOf(Filter.HOME, Filter.PUBLIC),
expiresAt = null,
irreversible = false,
wholeWord = true
),
Filter(
id = "123",
phrase = "wrongContext",
context = listOf(Filter.PUBLIC),
expiresAt = null,
irreversible = false,
wholeWord = true
)
)
)
)
}
})
activity.mastodonApi = apiMock
controller.create().start()
fragment.mastodonApi = apiMock
activity.supportFragmentManager.beginTransaction()
.replace(R.id.activity_main, fragment, "fragment")
.commit()
fragment.reloadFilters(false)
}
@Test
fun shouldNotFilter() {
assertFalse(fragment.shouldFilterStatus(
mockStatus(content = "should not be filtered")
))
}
@Test
fun shouldNotFilter_whenContextDoesNotMatch() {
assertFalse(fragment.shouldFilterStatus(
mockStatus(content = "one two wrongContext three")
))
}
@Test
fun shouldFilter_whenContentMatchesBadWord() {
assertTrue(fragment.shouldFilterStatus(
mockStatus(content = "one two badWord three")
))
}
@Test
fun shouldFilter_whenContentMatchesBadWordPart() {
assertTrue(fragment.shouldFilterStatus(
mockStatus(content = "one two badWordPart three")
))
}
@Test
fun shouldFilter_whenContentMatchesBadWholeWord() {
assertTrue(fragment.shouldFilterStatus(
mockStatus(content = "one two badWholeWord three")
))
}
@Test
fun shouldNotFilter_whenContentDoesNotMAtchWholeWord() {
assertFalse(fragment.shouldFilterStatus(
mockStatus(content = "one two badWholeWordTest three")
))
}
@Test
fun shouldFilter_whenSpoilerTextDoesMatch() {
assertTrue(fragment.shouldFilterStatus(
mockStatus(
content = "should not be filtered",
spoilerText = "badWord should be filtered"
)
))
}
@Test
fun shouldFilter_whenPollTextDoesMatch() {
assertTrue(fragment.shouldFilterStatus(
mockStatus(
content = "should not be filtered",
spoilerText = "should not be filtered",
pollOptions = listOf("should not be filtered", "badWord")
)
))
}
private fun mockStatus(
content: String = "",
spoilerText: String = "",
pollOptions: List<String>? = null
): Status {
return Status(
id = "123",
url = "https://mastodon.social/@Tusky/100571663297225812",
account = mock(),
inReplyToId = null,
inReplyToAccountId = null,
reblog = null,
content = SpannedString(content),
createdAt = Date(),
emojis = emptyList(),
reblogsCount = 0,
favouritesCount = 0,
reblogged = false,
favourited = false,
sensitive = false,
spoilerText = spoilerText,
visibility = Status.Visibility.PUBLIC,
attachments = arrayListOf(),
mentions = emptyArray(),
application = null,
pinned = false,
poll = if (pollOptions != null) {
Poll(
id = "1234",
expiresAt = null,
expired = false,
multiple = false,
votesCount = 0,
options = pollOptions.map {
PollOption(it, 0)
},
voted = false
)
} else null,
card = null
)
}
}
class FakeActivity: BottomSheetActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
class FakeFragment: SFragment() {
override fun removeItem(position: Int) {
}
override fun onReblog(reblog: Boolean, position: Int) {
}
override fun filterIsRelevant(filter: Filter): Boolean {
return filter.context.contains(Filter.HOME)
}
}

View File

@ -0,0 +1,3 @@
Tusky v9.1
This release ensures compatibility with Mastodon 3 and improves performance and stability.

View File

@ -0,0 +1,11 @@
تاسکی نسخه ۶٫۰
- صافی‌های خط‌زمانی به ترجیحات حساب منتقل شده و با کارساز همگام خواهد شد
- اکنون می‌توانید هشتگ سفارشی را به عنوان یک زبانه در رابط اصلی داشته باشید
- سیاهه‌ها می‌توانند ویرایش شوند
- امنیتی: حذف پشتیبانی برای TLS 1.0 و TLS 1.1، و افزودن پیشتیبانی برای TLS 1.3 روی اندروید ۶ به بالا
- نمای ایجاد با شروع نگارش، شکلک‌های سفارشی پیشنهاد می‌دهد
- سبک جدید «پیروی از سبک سیستم» در تنظیمات
- بهبود دسترس‌پذییری خط‌زمانی
- تاسکی اکنون اعلان‌های ناشناس را نادیده می‌گید و دیگر فرونمی‌پاشد
-

View File

@ -0,0 +1,7 @@
تاسکی نسخه ۷٫۰
- پشتیبانی از نمایش نظرسنجی‌ها، رای‌ها و اعلان‌های مربوط به نظرسنجی
- دکمه جدید برای از صافی گذراندن زبانه اعلان و برای پاک کردن همه اعلان‌ها
- امکان پاک کردن و بازنویسی بوق‌تان
- علامت جدید روی تصویر نمایه که نشان می‌دهد یک حساب، بات است یا نه (قابل غیرفعال‌سازی در ترجیحات)
- ترجمه‌های جدید: زبان‌های نروژی و اسلونیایی.

View File

@ -0,0 +1,9 @@
تاسکی نسخه ۹٫۰
- می‌توانید داخل تاسکی، نظرسنجی ایجاد کنید
- بهبود جستجو
- گزینه جدید در ترجیحات حساب برای اینکه همواره بوق‌های دارای محتوای حساس کامل نمایش داده شوند
- آواتارها در کشوی ناوبری اکنون به صورت مربع دارای لبه‌های گرد دیده می‌شوند
- اکنون امکان گزارش کاربرانی که هیچ بوقی ندارند فراهم شده است
- تاسکی اکنون از اتصال غیر ایمن در اندروید ۶ سربازمی‌زند
- به همراه بسیاری بهبودها و رفع اشکالات

View File

@ -0,0 +1,3 @@
تاسکی نسخه ۹٫۱
این نسخه، سازگاری با ماستودون ۳ را تامین کرده و دارای بهبود عملکرد و پایداری است.

View File

@ -0,0 +1,12 @@
تاسکی یک کارخواه سبک برای ماستودون (کارساز شبکه احتماعی آزاد و متن‌باز) است.
• دارای طراحی متریال
• اکثر APIهای ماستودون پیاده‌سازی شده است
• پشتیبانی از چند حساب
• پشتیبانی از سبک تیره و روشن با امکان تنظیم تغییر خودکار بر اساس ساعت روز
• پشتیبانی از پیش‌نویس - امکان نوشتن بوق‌ها و ذخیره‌شان برای بعد
• امکان انتخاب از بین انواع شکلک
• بهینه‌سازی‌شده برای صفحه‌هایی با اندازه‌های مختلف
• کاملا آزاد و متن‌باز - بدون وابستگی‌های غیرآزاد به خدمات گوگل
برای دریافت اطلاعات بیشتر درباره ماستودون https://joinmastodon.org را ببینید

View File

@ -0,0 +1 @@
یک کارخواه چندحسابی برای شبکه اجتماعی ماستودون

View File

@ -0,0 +1 @@
تاسکی

View File

@ -0,0 +1,9 @@
Tusky v9.0
- Indíthatsz szavazást Tusky-ból
- Fejlettebb keresés
- Új opció a fiók beállításainál a tartalom-figyelmeztetések alapértelmezett mutatására
- A navigációs fióknál az avataroknak lekerekített négyzet alakja van
- Be lehet jelenteni olyan fiókokat is, akik még sosem posztoltak
- A Tusky nem fog csatlakozni titkosítatlan kapcsolaton Android 6+-on
- Egy rakás kisebb fejlesztés és bugfix

View File

@ -0,0 +1,3 @@
Tusky v9.1
Denne versjonen sørger for kompatibilitet med Mastodon 3, og forbedrer ytelse og stabilitet.

View File

@ -0,0 +1,9 @@
Tusky v9.0
- Agora você pode criar enquetes no Tusky
- Pesquisa melhorada
- Nova opção nas preferências da conta: "Sempre expandir toots com Aviso de Conteúdo"
- Avatares em formato quadrado arredondado
- Agora é possível denunciar usuários mesmo sem toots
- Tusky se recusará a se conectar através de conexões de texto claro em Android 6+
- Muitas outras pequenas melhorias e correções de bugs