summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdolfo Santiago <epoch@nixnetmail.com>2023-02-25 09:04:55 +0100
committerAdolfo Santiago <epoch@nixnetmail.com>2023-02-25 09:04:55 +0100
commitf6c18ed1eb91f0c4ca0095ad049a3095fb3d1f69 (patch)
tree2d9498c26c8eb58e63719e37b915a85610c5c877
parent149022041b9f2f94b4f6052bd10d75d0a4b8c8a7 (diff)
Update Java formattingtest_spotless
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java72
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java95
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java13
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java35
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java8
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java17
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java54
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java27
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java30
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java27
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutedStatusViewHolder.java85
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java61
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java609
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.java23
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/SavedTootAdapter.java20
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/SingleViewHolder.java10
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java898
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java116
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java60
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java27
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java98
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/adapter/UnicodeEmojiAdapter.java100
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/common/DownsizeImageTask.java51
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java98
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java42
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java27
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressTextView.java34
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java120
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java517
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/core/utils/image/SvgModule.java8
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java849
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java9
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java35
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java2
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java1193
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java640
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java7
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java1320
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java481
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java6
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java2
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java2
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java36
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java13
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java12
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/BBCodeEdit.java13
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/CustomURLSpan.java25
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/Emojis.java2160
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/HTMLEdit.java18
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.java5
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java1
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java103
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/PagingRequestHelper.java240
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/PairedList.java21
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java15
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java3
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/TimestampUtils.java2
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java3
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java57
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/util/ViewPager2Fix.java59
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java5
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/view/EmojiKeyboard.java99
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java1
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java70
-rw-r--r--husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java285
-rw-r--r--husky/app/src/test/java/com/keylesspalace/tusky/util/ViewPager2FixTest.java4
66 files changed, 6127 insertions, 5051 deletions
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/husky/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
index 4b19039..36f670d 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
@@ -15,6 +15,8 @@
package com.keylesspalace.tusky;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -44,7 +46,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import kotlin.Lazy;
-import static org.koin.java.KoinJavaComponent.inject;
public abstract class BaseActivity extends AppCompatActivity {
@@ -64,7 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity {
* views are created. */
String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT);
Log.d("activeTheme", theme);
- if(theme.equals("black")) {
+ if (theme.equals("black")) {
setTheme(R.style.TuskyBlackTheme);
}
@@ -74,12 +75,12 @@ public abstract class BaseActivity extends AppCompatActivity {
int recentsBackgroundColor = ThemeUtils.getColor(this, R.attr.colorSurface);
setTaskDescription(
- new ActivityManager.TaskDescription(appName, appIcon, recentsBackgroundColor));
+ new ActivityManager.TaskDescription(appName, appIcon, recentsBackgroundColor));
int style = textStyle(preferences.getString("statusTextSize", "medium"));
getTheme().applyStyle(style, false);
- if(requiresLogin()) {
+ if (requiresLogin()) {
redirectIfNotLoggedIn();
}
@@ -97,7 +98,7 @@ public abstract class BaseActivity extends AppCompatActivity {
private static int textStyle(String name) {
int style;
- switch(name) {
+ switch (name) {
case "smallest":
style = R.style.TextSizeSmallest;
break;
@@ -135,7 +136,7 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void redirectIfNotLoggedIn() {
AccountEntity account = accountManager.getValue().getActiveAccount();
- if(account == null) {
+ if (account == null) {
Intent intent = new Intent(this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityWithSlideInAnimation(intent);
@@ -143,33 +144,33 @@ public abstract class BaseActivity extends AppCompatActivity {
}
}
- protected void showErrorDialog(View anyView,
- @StringRes int descriptionId,
- @StringRes int actionId,
- View.OnClickListener listener)
- {
- if(anyView != null) {
+ protected void showErrorDialog(
+ View anyView,
+ @StringRes int descriptionId,
+ @StringRes int actionId,
+ View.OnClickListener listener) {
+ if (anyView != null) {
Snackbar bar = Snackbar.make(anyView, getString(descriptionId), Snackbar.LENGTH_SHORT);
bar.setAction(actionId, listener);
bar.show();
}
}
- public void showAccountChooserDialog(CharSequence dialogTitle,
- boolean showActiveAccount,
- AccountSelectionListener listener)
- {
+ public void showAccountChooserDialog(
+ CharSequence dialogTitle,
+ boolean showActiveAccount,
+ AccountSelectionListener listener) {
List<AccountEntity> accounts = accountManager.getValue().getAllAccountsOrderedByActive();
AccountEntity activeAccount = accountManager.getValue().getActiveAccount();
- switch(accounts.size()) {
+ switch (accounts.size()) {
case 1:
listener.onAccountSelected(activeAccount);
return;
case 2:
- if(!showActiveAccount) {
- for(AccountEntity account : accounts) {
- if(activeAccount != account) {
+ if (!showActiveAccount) {
+ for (AccountEntity account : accounts) {
+ if (activeAccount != account) {
listener.onAccountSelected(account);
return;
}
@@ -178,22 +179,24 @@ public abstract class BaseActivity extends AppCompatActivity {
break;
}
- if(!showActiveAccount && activeAccount != null) {
+ if (!showActiveAccount && activeAccount != null) {
accounts.remove(activeAccount);
}
AccountSelectionAdapter adapter = new AccountSelectionAdapter(this);
adapter.addAll(accounts);
- new AlertDialog.Builder(this).setTitle(dialogTitle).setAdapter(adapter,
- (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index))).show();
+ new AlertDialog.Builder(this)
+ .setTitle(dialogTitle)
+ .setAdapter(
+ adapter,
+ (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index)))
+ .show();
}
@Override
- public void onRequestPermissionsResult(int requestCode,
- @NonNull String[] permissions,
- @NonNull int[] grantResults)
- {
- if(requesters.containsKey(requestCode)) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requesters.containsKey(requestCode)) {
PermissionRequester requester = requesters.remove(requestCode);
requester.onRequestPermissionsResult(permissions, grantResults);
}
@@ -201,15 +204,15 @@ public abstract class BaseActivity extends AppCompatActivity {
public void requestPermissions(String[] permissions, PermissionRequester requester) {
ArrayList<String> permissionsToRequest = new ArrayList<>();
- for(String permission : permissions) {
- if(ContextCompat.checkSelfPermission(this, permission) !=
- PackageManager.PERMISSION_GRANTED) {
+ for (String permission : permissions) {
+ if (ContextCompat.checkSelfPermission(this, permission)
+ != PackageManager.PERMISSION_GRANTED) {
permissionsToRequest.add(permission);
}
}
- if(permissionsToRequest.isEmpty()) {
+ if (permissionsToRequest.isEmpty()) {
int[] permissionsAlreadyGranted = new int[permissions.length];
- for(int i = 0; i < permissionsAlreadyGranted.length; ++i) {
+ for (int i = 0; i < permissionsAlreadyGranted.length; ++i) {
permissionsAlreadyGranted[i] = PackageManager.PERMISSION_GRANTED;
}
requester.onRequestPermissionsResult(permissions, permissionsAlreadyGranted);
@@ -217,12 +220,11 @@ public abstract class BaseActivity extends AppCompatActivity {
}
int newKey = requester == null ? REQUESTER_NONE : requesters.size();
- if(newKey != REQUESTER_NONE) {
+ if (newKey != REQUESTER_NONE) {
requesters.put(newKey, requester);
}
String[] permissionsCopy = new String[permissionsToRequest.size()];
permissionsToRequest.toArray(permissionsCopy);
ActivityCompat.requestPermissions(this, permissionsCopy, newKey);
-
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/husky/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java
index a459af4..cc19d49 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java
@@ -15,6 +15,11 @@
package com.keylesspalace.tusky;
+import static com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions;
+import static com.uber.autodispose.AutoDispose.autoDisposable;
+import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -33,50 +38,45 @@ import com.keylesspalace.tusky.adapter.SavedTootAdapter;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.StatusComposedEvent;
import com.keylesspalace.tusky.components.compose.ComposeActivity;
-import static com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions;
import com.keylesspalace.tusky.db.AppDatabase;
import com.keylesspalace.tusky.db.TootDao;
import com.keylesspalace.tusky.db.TootEntity;
import com.keylesspalace.tusky.util.SaveTootHelper;
import com.keylesspalace.tusky.view.BackgroundMessageView;
-import static com.uber.autodispose.AutoDispose.autoDisposable;
-import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
-import static org.koin.java.KoinJavaComponent.inject;
public final class SavedTootActivity extends BaseActivity
- implements SavedTootAdapter.SavedTootAction
-{
+ implements SavedTootAdapter.SavedTootAction {
// ui
private SavedTootAdapter adapter;
private BackgroundMessageView errorMessageView;
private List<TootEntity> toots = new ArrayList<>();
- @Nullable
- private AsyncTask<?, ?, ?> asyncTask;
+ @Nullable private AsyncTask<?, ?, ?> asyncTask;
private final EventHub eventHub = (EventHub) inject(EventHub.class).getValue();
private final AppDatabase database = (AppDatabase) inject(AppDatabase.class).getValue();
private final SaveTootHelper saveTootHelper =
- (SaveTootHelper) inject(SaveTootHelper.class).getValue();
+ (SaveTootHelper) inject(SaveTootHelper.class).getValue();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- eventHub.getEvents().observeOn(AndroidSchedulers.mainThread())
- .ofType(StatusComposedEvent.class)
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((__) -> this.fetchToots());
+ eventHub.getEvents()
+ .observeOn(AndroidSchedulers.mainThread())
+ .ofType(StatusComposedEvent.class)
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe((__) -> this.fetchToots());
setContentView(R.layout.activity_saved_toot);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar bar = getSupportActionBar();
- if(bar != null) {
+ if (bar != null) {
bar.setTitle(getString(R.string.title_drafts));
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowHomeEnabled(true);
@@ -88,7 +88,7 @@ public final class SavedTootActivity extends BaseActivity
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
DividerItemDecoration divider =
- new DividerItemDecoration(this, layoutManager.getOrientation());
+ new DividerItemDecoration(this, layoutManager.getOrientation());
recyclerView.addItemDecoration(divider);
adapter = new SavedTootAdapter(this);
recyclerView.setAdapter(adapter);
@@ -103,31 +103,33 @@ public final class SavedTootActivity extends BaseActivity
@Override
protected void onPause() {
super.onPause();
- if(asyncTask != null) {
+ if (asyncTask != null) {
asyncTask.cancel(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
- case android.R.id.home: {
- onBackPressed();
- return true;
- }
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ {
+ onBackPressed();
+ return true;
+ }
}
return super.onOptionsItemSelected(item);
}
private void fetchToots() {
- asyncTask = new FetchPojosTask(this, database.tootDao()).executeOnExecutor(
- AsyncTask.THREAD_POOL_EXECUTOR);
+ asyncTask =
+ new FetchPojosTask(this, database.tootDao())
+ .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setNoContent(int size) {
- if(size == 0) {
- errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_saved_status,
- null);
+ if (size == 0) {
+ errorMessageView.setup(
+ R.drawable.elephant_friend_empty, R.string.no_saved_status, null);
errorMessageView.setVisibility(View.VISIBLE);
} else {
errorMessageView.setVisibility(View.GONE);
@@ -141,7 +143,7 @@ public final class SavedTootActivity extends BaseActivity
toots.remove(position);
// update adapter
- if(adapter != null) {
+ if (adapter != null) {
adapter.removeItem(position);
setNoContent(toots.size());
}
@@ -150,23 +152,32 @@ public final class SavedTootActivity extends BaseActivity
@Override
public void click(int position, TootEntity item) {
Gson gson = new Gson();
- Type stringListType = new TypeToken<List<String>>() {
- }.getType();
+ Type stringListType = new TypeToken<List<String>>() {}.getType();
List<String> jsonUrls = gson.fromJson(item.getUrls(), stringListType);
List<String> descriptions = gson.fromJson(item.getDescriptions(), stringListType);
- ComposeOptions composeOptions = new ComposeOptions(
- /*scheduledTootUid*/null, item.getUid(),
- /*drafId*/null, item.getText(), jsonUrls, descriptions,
- /*mentionedUsernames*/null, item.getInReplyToId(),
- /*replyVisibility*/null, item.getVisibility(), item.getContentWarning(),
- item.getInReplyToUsername(), item.getInReplyToText(),
- /*mediaAttachments*/null,
- /*draftAttachments*/null,
- /*scheduledAt*/null,
- /*sensitive*/null,
- /*poll*/null, item.getFormattingSyntax(),
- /* modifiedInitialState */ true);
+ ComposeOptions composeOptions =
+ new ComposeOptions(
+ /*scheduledTootUid*/ null,
+ item.getUid(),
+ /*drafId*/ null,
+ item.getText(),
+ jsonUrls,
+ descriptions,
+ /*mentionedUsernames*/ null,
+ item.getInReplyToId(),
+ /*replyVisibility*/ null,
+ item.getVisibility(),
+ item.getContentWarning(),
+ item.getInReplyToUsername(),
+ item.getInReplyToText(),
+ /*mediaAttachments*/ null,
+ /*draftAttachments*/ null,
+ /*scheduledAt*/ null,
+ /*sensitive*/ null,
+ /*poll*/ null,
+ item.getFormattingSyntax(),
+ /* modifiedInitialState */ true);
Intent intent = ComposeActivity.startIntent(this, composeOptions);
startActivity(intent);
}
@@ -190,7 +201,7 @@ public final class SavedTootActivity extends BaseActivity
protected void onPostExecute(List<TootEntity> pojos) {
super.onPostExecute(pojos);
SavedTootActivity activity = activityRef.get();
- if(activity == null) {
+ if (activity == null) {
return;
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/husky/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
index 9d18494..3ca6fba 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
@@ -48,7 +48,7 @@ public class ViewTagActivity extends BottomSheetActivity {
setSupportActionBar(toolbar);
ActionBar bar = getSupportActionBar();
- if(bar != null) {
+ if (bar != null) {
bar.setTitle(String.format(getString(R.string.title_tag), hashtag));
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowHomeEnabled(true);
@@ -62,11 +62,12 @@ public class ViewTagActivity extends BottomSheetActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
- case android.R.id.home: {
- onBackPressed();
- return true;
- }
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ {
+ onBackPressed();
+ return true;
+ }
}
return super.onOptionsItemSelected(item);
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/husky/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
index 74a8f79..39e4048 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
@@ -55,7 +55,7 @@ public class ViewThreadActivity extends BottomSheetActivity {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- if(actionBar != null) {
+ if (actionBar != null) {
actionBar.setTitle(R.string.title_view_thread);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
@@ -64,8 +64,9 @@ public class ViewThreadActivity extends BottomSheetActivity {
String id = getIntent().getStringExtra(ID_EXTRA);
fragment =
- (ViewThreadFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG + id);
- if(fragment == null) {
+ (ViewThreadFragment)
+ getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG + id);
+ if (fragment == null) {
fragment = ViewThreadFragment.newInstance(id);
}
@@ -79,13 +80,15 @@ public class ViewThreadActivity extends BottomSheetActivity {
getMenuInflater().inflate(R.menu.view_thread_toolbar, menu);
MenuItem menuItem = menu.findItem(R.id.action_reveal);
menuItem.setVisible(revealButtonState != REVEAL_BUTTON_HIDDEN);
- menuItem.setIcon(revealButtonState == REVEAL_BUTTON_REVEAL ? R.drawable.ic_eye_24dp :
- R.drawable.ic_hide_media_24dp);
+ menuItem.setIcon(
+ revealButtonState == REVEAL_BUTTON_REVEAL
+ ? R.drawable.ic_eye_24dp
+ : R.drawable.ic_hide_media_24dp);
return super.onCreateOptionsMenu(menu);
}
public void setRevealButtonState(int state) {
- switch(state) {
+ switch (state) {
case REVEAL_BUTTON_HIDDEN:
case REVEAL_BUTTON_REVEAL:
case REVEAL_BUTTON_HIDE:
@@ -99,15 +102,17 @@ public class ViewThreadActivity extends BottomSheetActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
- case android.R.id.home: {
- onBackPressed();
- return true;
- }
- case R.id.action_reveal: {
- fragment.onRevealPressed();
- return true;
- }
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ {
+ onBackPressed();
+ return true;
+ }
+ case R.id.action_reveal:
+ {
+ fragment.onRevealPressed();
+ return true;
+ }
}
return super.onOptionsItemSelected(item);
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
index 5c52e39..778fc05 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
@@ -18,11 +18,9 @@ package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.util.ListUtils;
-
import java.util.ArrayList;
import java.util.List;
@@ -70,11 +68,11 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
public void setBottomLoading(boolean loading) {
boolean wasLoading = bottomLoading;
- if(wasLoading == loading) {
+ if (wasLoading == loading) {
return;
}
bottomLoading = loading;
- if(loading) {
+ if (loading) {
notifyItemInserted(accountList.size());
} else {
notifyItemRemoved(accountList.size());
@@ -107,6 +105,4 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
accountList.add(position, account);
notifyItemInserted(position);
}
-
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
index 3ac5968..1b8672e 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
@@ -4,10 +4,8 @@ import android.content.SharedPreferences;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
@@ -30,7 +28,8 @@ public class AccountViewHolder extends RecyclerView.ViewHolder {
displayName = itemView.findViewById(R.id.account_display_name);
avatar = itemView.findViewById(R.id.account_avatar);
avatarInset = itemView.findViewById(R.id.account_avatar_inset);
- SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext());
+ SharedPreferences sharedPrefs =
+ PreferenceManager.getDefaultSharedPreferences(itemView.getContext());
showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true);
animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false);
}
@@ -40,10 +39,14 @@ public class AccountViewHolder extends RecyclerView.ViewHolder {
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername);
- CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, true);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(
+ account.getName(), account.getEmojis(), displayName, true);
displayName.setText(emojifiedName);
- int avatarRadius = avatar.getContext().getResources()
- .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
+ int avatarRadius =
+ avatar.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
if (showBotOverlay && account.getBot()) {
avatarInset.setVisibility(View.VISIBLE);
@@ -61,4 +64,4 @@ public class AccountViewHolder extends RecyclerView.ViewHolder {
public void setupLinkListener(final LinkListener listener) {
itemView.setOnClickListener(v -> listener.onViewAccount(accountId));
}
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
index 073d76d..77ca437 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
@@ -21,11 +21,9 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
@@ -43,16 +41,20 @@ public class BlocksAdapter extends AccountAdapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_ACCOUNT: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_blocked_user, parent, false);
- return new BlockedUserViewHolder(view);
- }
- case VIEW_TYPE_FOOTER: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_footer, parent, false);
- return new LoadingFooterViewHolder(view);
- }
+ case VIEW_TYPE_ACCOUNT:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_blocked_user, parent, false);
+ return new BlockedUserViewHolder(view);
+ }
+ case VIEW_TYPE_FOOTER:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_footer, parent, false);
+ return new LoadingFooterViewHolder(view);
+ }
}
}
@@ -79,30 +81,34 @@ public class BlocksAdapter extends AccountAdapter {
username = itemView.findViewById(R.id.blocked_user_username);
displayName = itemView.findViewById(R.id.blocked_user_display_name);
unblock = itemView.findViewById(R.id.blocked_user_unblock);
- animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
- .getBoolean("animateGifAvatars", false);
-
+ animateAvatar =
+ PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
+ .getBoolean("animateGifAvatars", false);
}
void setupWithAccount(Account account) {
id = account.getId();
- CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName);
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername);
- int avatarRadius = avatar.getContext().getResources()
- .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
+ int avatarRadius =
+ avatar.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
}
void setupActionListener(final AccountActionListener listener) {
- unblock.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onBlock(false, id, position);
- }
- });
+ unblock.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onBlock(false, id, position);
+ }
+ });
itemView.setOnClickListener(v -> listener.onViewAccount(id));
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java
index 658c28d..ef22f8a 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiReactionsAdapter.java
@@ -39,7 +39,10 @@ public class EmojiReactionsAdapter extends RecyclerView.Adapter<SingleViewHolder
private final StatusActionListener listener;
private final String statusId;
- EmojiReactionsAdapter(final List<EmojiReaction> reactions, final StatusActionListener listener, final String statusId) {
+ EmojiReactionsAdapter(
+ final List<EmojiReaction> reactions,
+ final StatusActionListener listener,
+ final String statusId) {
this.reactions = reactions;
this.listener = listener;
this.statusId = statusId;
@@ -47,32 +50,33 @@ public class EmojiReactionsAdapter extends RecyclerView.Adapter<SingleViewHolder
@Override
public SingleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_emoji_reaction, parent, false);
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_emoji_reaction, parent, false);
return new SingleViewHolder(view);
}
@Override
public void onBindViewHolder(SingleViewHolder holder, int position) {
EmojiReaction reaction = reactions.get(position);
- SpannableStringBuilder builder = new SpannableStringBuilder(
- reaction.getName() + " " + reaction.getCount());
+ SpannableStringBuilder builder =
+ new SpannableStringBuilder(reaction.getName() + " " + reaction.getCount());
EmojiAppCompatButton btn = (EmojiAppCompatButton) holder.itemView;
var url = reaction.getUrl();
- if(url != null) {
+ if (url != null) {
var span = CustomEmojiHelper.createEmojiSpan(url, btn, true);
- builder.setSpan(span, 0, reaction.getName().length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(span, 0, reaction.getName().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
btn.setText(builder, BufferType.SPANNABLE);
btn.setActivated(reaction.getMe());
- btn.setOnClickListener(v -> {
- listener.onEmojiReactMenu(v, reaction, statusId);
- });
+ btn.setOnClickListener(
+ v -> {
+ listener.onEmojiReactMenu(v, reaction, statusId);
+ });
}
// total number of rows
@@ -81,4 +85,3 @@ public class EmojiReactionsAdapter extends RecyclerView.Adapter<SingleViewHolder
return reactions.size();
}
}
-
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
index 8215874..3ecc921 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
@@ -15,12 +15,11 @@
package com.keylesspalace.tusky.adapter;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
@@ -36,16 +35,20 @@ public class FollowAdapter extends AccountAdapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_ACCOUNT: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_account, parent, false);
- return new AccountViewHolder(view);
- }
- case VIEW_TYPE_FOOTER: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_footer, parent, false);
- return new LoadingFooterViewHolder(view);
- }
+ case VIEW_TYPE_ACCOUNT:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_account, parent, false);
+ return new AccountViewHolder(view);
+ }
+ case VIEW_TYPE_FOOTER:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_footer, parent, false);
+ return new LoadingFooterViewHolder(view);
+ }
}
}
@@ -57,5 +60,4 @@ public class FollowAdapter extends AccountAdapter {
holder.setupActionListener(accountActionListener);
}
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
index 307823b..788cae3 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
@@ -18,10 +18,8 @@ package com.keylesspalace.tusky.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
@@ -37,17 +35,20 @@ public class FollowRequestsAdapter extends AccountAdapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_ACCOUNT: {
- ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(
- LayoutInflater.from(parent.getContext()), parent, false
- );
- return new FollowRequestViewHolder(binding, false);
- }
- case VIEW_TYPE_FOOTER: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_footer, parent, false);
- return new LoadingFooterViewHolder(view);
- }
+ case VIEW_TYPE_ACCOUNT:
+ {
+ ItemFollowRequestBinding binding =
+ ItemFollowRequestBinding.inflate(
+ LayoutInflater.from(parent.getContext()), parent, false);
+ return new FollowRequestViewHolder(binding, false);
+ }
+ case VIEW_TYPE_FOOTER:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_footer, parent, false);
+ return new LoadingFooterViewHolder(view);
+ }
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutedStatusViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutedStatusViewHolder.java
index 66138b2..36374ee 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutedStatusViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutedStatusViewHolder.java
@@ -5,11 +5,9 @@ import android.text.format.DateUtils;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
@@ -17,7 +15,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.TimestampUtils;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@@ -42,13 +39,14 @@ public class MutedStatusViewHolder extends RecyclerView.ViewHolder {
username = itemView.findViewById(R.id.status_username);
timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
unmuteButton = itemView.findViewById(R.id.status_toggle_mute);
-
+
this.shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
this.longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
}
protected void setDisplayName(String name, List<Emoji> customEmojis) {
- CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName, true);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(name, customEmojis, displayName, true);
displayName.setText(emojifiedName);
}
@@ -67,7 +65,9 @@ public class MutedStatusViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
- String readout = TimestampUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
+ String readout =
+ TimestampUtils.getRelativeTimeSpanString(
+ timestampInfo.getContext(), then, now);
timestampInfo.setText(readout);
}
}
@@ -84,8 +84,8 @@ public class MutedStatusViewHolder extends RecyclerView.ViewHolder {
}
}
- private CharSequence getCreatedAtDescription(Date createdAt,
- StatusDisplayOptions statusDisplayOptions) {
+ private CharSequence getCreatedAtDescription(
+ Date createdAt, StatusDisplayOptions statusDisplayOptions) {
if (statusDisplayOptions.useAbsoluteTime()) {
return getAbsoluteTime(createdAt);
} else {
@@ -97,57 +97,61 @@ public class MutedStatusViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
- return DateUtils.getRelativeTimeSpanString(then, now,
- DateUtils.SECOND_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE);
+ return DateUtils.getRelativeTimeSpanString(
+ then, now, DateUtils.SECOND_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
}
}
}
- private void setDescriptionForStatus(@NonNull StatusViewData.Concrete status,
- StatusDisplayOptions statusDisplayOptions) {
+ private void setDescriptionForStatus(
+ @NonNull StatusViewData.Concrete status, StatusDisplayOptions statusDisplayOptions) {
Context context = itemView.getContext();
- String description = context.getString(R.string.description_muted_status,
- status.getUserFullName(),
- getCreatedAtDescription(status.getCreatedAt(), statusDisplayOptions),
- status.getNickname()
- );
+ String description =
+ context.getString(
+ R.string.description_muted_status,
+ status.getUserFullName(),
+ getCreatedAtDescription(status.getCreatedAt(), statusDisplayOptions),
+ status.getNickname());
itemView.setContentDescription(description);
}
-
protected void setupButtons(final StatusActionListener listener, final String accountId) {
-
- unmuteButton.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onMute(position, false);
- }
- });
-
- itemView.setOnClickListener( v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onViewThread(position);
- }
- });
+
+ unmuteButton.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onMute(position, false);
+ }
+ });
+
+ itemView.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onViewThread(position);
+ }
+ });
}
- public void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions) {
+ public void setupWithStatus(
+ StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions) {
this.setupWithStatus(status, listener, statusDisplayOptions, null);
}
- protected void setupWithStatus(StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
- @Nullable Object payloads) {
+ protected void setupWithStatus(
+ StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions,
+ @Nullable Object payloads) {
if (payloads == null) {
setDisplayName(status.getUserFullName(), status.getAccountEmojis());
setUsername(status.getNickname());
setCreatedAt(status.getCreatedAt(), statusDisplayOptions);
-
+
setupButtons(listener, status.getSenderId());
setDescriptionForStatus(status, statusDisplayOptions);
@@ -164,7 +168,6 @@ public class MutedStatusViewHolder extends RecyclerView.ViewHolder {
setCreatedAt(status.getCreatedAt(), statusDisplayOptions);
}
}
-
}
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
index c4224c9..3f67c37 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
@@ -6,18 +6,15 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
-
import java.util.HashMap;
public class MutesAdapter extends AccountAdapter {
@@ -33,16 +30,20 @@ public class MutesAdapter extends AccountAdapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_ACCOUNT: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_muted_user, parent, false);
- return new MutesAdapter.MutedUserViewHolder(view);
- }
- case VIEW_TYPE_FOOTER: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_footer, parent, false);
- return new LoadingFooterViewHolder(view);
- }
+ case VIEW_TYPE_ACCOUNT:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_muted_user, parent, false);
+ return new MutesAdapter.MutedUserViewHolder(view);
+ }
+ case VIEW_TYPE_FOOTER:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_footer, parent, false);
+ return new LoadingFooterViewHolder(view);
+ }
}
}
@@ -83,22 +84,27 @@ public class MutesAdapter extends AccountAdapter {
displayName = itemView.findViewById(R.id.muted_user_display_name);
unmute = itemView.findViewById(R.id.muted_user_unmute);
muteNotifications = itemView.findViewById(R.id.muted_user_mute_notifications);
- animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
- .getBoolean("animateGifAvatars", false);
+ animateAvatar =
+ PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
+ .getBoolean("animateGifAvatars", false);
}
void setupWithAccount(Account account, Boolean mutingNotifications) {
id = account.getId();
- CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName);
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername);
- int avatarRadius = avatar.getContext().getResources()
- .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
+ int avatarRadius =
+ avatar.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
- String unmuteString = unmute.getContext().getString(R.string.action_unmute_desc, formattedUsername);
+ String unmuteString =
+ unmute.getContext().getString(R.string.action_unmute_desc, formattedUsername);
unmute.setContentDescription(unmuteString);
ViewCompat.setTooltipText(unmute, unmuteString);
@@ -112,14 +118,21 @@ public class MutesAdapter extends AccountAdapter {
if (notifications) {
muteNotifications.setImageResource(R.drawable.ic_notifications_24dp);
- String unmuteNotificationsString = muteNotifications.getContext()
- .getString(R.string.action_unmute_notifications_desc, formattedUsername);
+ String unmuteNotificationsString =
+ muteNotifications
+ .getContext()
+ .getString(
+ R.string.action_unmute_notifications_desc,
+ formattedUsername);
muteNotifications.setContentDescription(unmuteNotificationsString);
ViewCompat.setTooltipText(muteNotifications, unmuteNotificationsString);
} else {
muteNotifications.setImageResource(R.drawable.ic_notifications_off_24dp);
- String muteNotificationsString = muteNotifications.getContext()
- .getString(R.string.action_mute_notifications_desc, formattedUsername);
+ String muteNotificationsString =
+ muteNotifications
+ .getContext()
+ .getString(
+ R.string.action_mute_notifications_desc, formattedUsername);
muteNotifications.setContentDescription(muteNotificationsString);
ViewCompat.setTooltipText(muteNotifications, muteNotificationsString);
}
@@ -128,7 +141,7 @@ public class MutesAdapter extends AccountAdapter {
void setupActionListener(final AccountActionListener listener) {
unmute.setOnClickListener(v -> listener.onMute(false, id, getAdapterPosition(), false));
muteNotifications.setOnClickListener(
- v -> listener.onMute(true, id, getAdapterPosition(), !notifications));
+ v -> listener.onMute(true, id, getAdapterPosition(), !notifications));
itemView.setOnClickListener(v -> listener.onViewAccount(id));
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 93a01a9..52e96a0 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -83,7 +83,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private static final int VIEW_TYPE_MOVE = 6;
private static final int VIEW_TYPE_UNKNOWN = 7;
- private static final InputFilter[] COLLAPSE_INPUT_FILTER = new InputFilter[]{SmartLengthInputFilter.INSTANCE};
+ private static final InputFilter[] COLLAPSE_INPUT_FILTER =
+ new InputFilter[] {SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];
private String accountId;
@@ -93,12 +94,13 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private AccountActionListener accountActionListener;
private AdapterDataSource<NotificationViewData> dataSource;
- public NotificationsAdapter(String accountId,
- AdapterDataSource<NotificationViewData> dataSource,
- StatusDisplayOptions statusDisplayOptions,
- StatusActionListener statusListener,
- NotificationActionListener notificationActionListener,
- AccountActionListener accountActionListener) {
+ public NotificationsAdapter(
+ String accountId,
+ AdapterDataSource<NotificationViewData> dataSource,
+ StatusDisplayOptions statusDisplayOptions,
+ StatusActionListener statusListener,
+ NotificationActionListener notificationActionListener,
+ AccountActionListener accountActionListener) {
this.accountId = accountId;
this.dataSource = dataSource;
@@ -113,51 +115,49 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
- case VIEW_TYPE_STATUS: {
- View view = inflater
- .inflate(R.layout.item_status, parent, false);
- return new StatusViewHolder(view);
- }
- case VIEW_TYPE_MUTED_STATUS: {
- View view = inflater
- .inflate(R.layout.item_status_muted, parent, false);
- return new MutedStatusViewHolder(view);
- }
- case VIEW_TYPE_STATUS_NOTIFICATION: {
- View view = inflater
- .inflate(R.layout.item_status_notification, parent, false);
- return new StatusNotificationViewHolder(view, statusDisplayOptions);
- }
+ case VIEW_TYPE_STATUS:
+ {
+ View view = inflater.inflate(R.layout.item_status, parent, false);
+ return new StatusViewHolder(view);
+ }
+ case VIEW_TYPE_MUTED_STATUS:
+ {
+ View view = inflater.inflate(R.layout.item_status_muted, parent, false);
+ return new MutedStatusViewHolder(view);
+ }
+ case VIEW_TYPE_STATUS_NOTIFICATION:
+ {
+ View view = inflater.inflate(R.layout.item_status_notification, parent, false);
+ return new StatusNotificationViewHolder(view, statusDisplayOptions);
+ }
case VIEW_TYPE_MOVE:
- case VIEW_TYPE_FOLLOW: {
- View view = inflater
- .inflate(R.layout.item_follow, parent, false);
- return new FollowViewHolder(view, statusDisplayOptions);
- }
- case VIEW_TYPE_FOLLOW_REQUEST: {
- ItemFollowRequestNotificationBinding binding =
- ItemFollowRequestNotificationBinding.inflate(
- LayoutInflater.from(parent.getContext()), parent, false
- );
- return new FollowRequestViewHolder(binding, true);
- }
- case VIEW_TYPE_PLACEHOLDER: {
- View view = inflater
- .inflate(R.layout.item_status_placeholder, parent, false);
- return new PlaceholderViewHolder(view);
- }
+ case VIEW_TYPE_FOLLOW:
+ {
+ View view = inflater.inflate(R.layout.item_follow, parent, false);
+ return new FollowViewHolder(view, statusDisplayOptions);
+ }
+ case VIEW_TYPE_FOLLOW_REQUEST:
+ {
+ ItemFollowRequestNotificationBinding binding =
+ ItemFollowRequestNotificationBinding.inflate(
+ LayoutInflater.from(parent.getContext()), parent, false);
+ return new FollowRequestViewHolder(binding, true);
+ }
+ case VIEW_TYPE_PLACEHOLDER:
+ {
+ View view = inflater.inflate(R.layout.item_status_placeholder, parent, false);
+ return new PlaceholderViewHolder(view);
+ }
default:
- case VIEW_TYPE_UNKNOWN: {
- View view = new View(parent.getContext());
- view.setLayoutParams(
- new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- Utils.dpToPx(parent.getContext(), 24)
- )
- );
- return new RecyclerView.ViewHolder(view) {
- };
- }
+ case VIEW_TYPE_UNKNOWN:
+ {
+ View view = new View(parent.getContext());
+ view.setLayoutParams(
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ Utils.dpToPx(parent.getContext(), 24)));
+ return new RecyclerView.ViewHolder(view) {};
+ }
}
}
@@ -167,17 +167,20 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
@Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
+ public void onBindViewHolder(
+ @NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
bindViewHolder(viewHolder, position, payloads);
}
- private void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads) {
+ private void bindViewHolder(
+ @NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads) {
Object payloadForHolder = payloads != null && !payloads.isEmpty() ? payloads.get(0) : null;
if (position < this.dataSource.getItemCount()) {
NotificationViewData notification = dataSource.getItemAt(position);
if (notification instanceof NotificationViewData.Placeholder) {
if (payloadForHolder == null) {
- NotificationViewData.Placeholder placeholder = ((NotificationViewData.Placeholder) notification);
+ NotificationViewData.Placeholder placeholder =
+ ((NotificationViewData.Placeholder) notification);
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(statusListener, placeholder.isLoading());
}
@@ -186,83 +189,104 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
NotificationViewData.Concrete concreteNotificaton =
(NotificationViewData.Concrete) notification;
switch (viewHolder.getItemViewType()) {
- case VIEW_TYPE_STATUS: {
- StatusViewHolder holder = (StatusViewHolder) viewHolder;
- StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
- holder.setupWithStatus(status,
- statusListener, statusDisplayOptions, payloadForHolder);
- if (concreteNotificaton.getType() == Notification.Type.POLL) {
- holder.setPollInfo(accountId.equals(concreteNotificaton.getAccount().getId()));
- } else {
- holder.hideStatusInfo();
- }
- break;
- }
- case VIEW_TYPE_MUTED_STATUS: {
- MutedStatusViewHolder holder = (MutedStatusViewHolder) viewHolder;
- StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
- holder.setupWithStatus(status,
- statusListener, statusDisplayOptions, payloadForHolder);
- break;
- }
- case VIEW_TYPE_STATUS_NOTIFICATION: {
- StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
- StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData();
- if (payloadForHolder == null) {
- if (statusViewData == null) {
- holder.showNotificationContent(false);
+ case VIEW_TYPE_STATUS:
+ {
+ StatusViewHolder holder = (StatusViewHolder) viewHolder;
+ StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
+ holder.setupWithStatus(
+ status, statusListener, statusDisplayOptions, payloadForHolder);
+ if (concreteNotificaton.getType() == Notification.Type.POLL) {
+ holder.setPollInfo(
+ accountId.equals(concreteNotificaton.getAccount().getId()));
} else {
- holder.showNotificationContent(true);
-
- holder.setDisplayName(statusViewData.getUserFullName(), statusViewData.getAccountEmojis());
- holder.setUsername(statusViewData.getNickname());
- holder.setCreatedAt(statusViewData.getCreatedAt());
-
- if(concreteNotificaton.getType() == Notification.Type.STATUS) {
- holder.setAvatar(statusViewData.getAvatar(), statusViewData.isBot());
+ holder.hideStatusInfo();
+ }
+ break;
+ }
+ case VIEW_TYPE_MUTED_STATUS:
+ {
+ MutedStatusViewHolder holder = (MutedStatusViewHolder) viewHolder;
+ StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
+ holder.setupWithStatus(
+ status, statusListener, statusDisplayOptions, payloadForHolder);
+ break;
+ }
+ case VIEW_TYPE_STATUS_NOTIFICATION:
+ {
+ StatusNotificationViewHolder holder =
+ (StatusNotificationViewHolder) viewHolder;
+ StatusViewData.Concrete statusViewData =
+ concreteNotificaton.getStatusViewData();
+ if (payloadForHolder == null) {
+ if (statusViewData == null) {
+ holder.showNotificationContent(false);
} else {
- holder.setAvatars(statusViewData.getAvatar(),
- concreteNotificaton.getAccount().getAvatar());
+ holder.showNotificationContent(true);
+
+ holder.setDisplayName(
+ statusViewData.getUserFullName(),
+ statusViewData.getAccountEmojis());
+ holder.setUsername(statusViewData.getNickname());
+ holder.setCreatedAt(statusViewData.getCreatedAt());
+
+ if (concreteNotificaton.getType() == Notification.Type.STATUS) {
+ holder.setAvatar(
+ statusViewData.getAvatar(), statusViewData.isBot());
+ } else {
+ holder.setAvatars(
+ statusViewData.getAvatar(),
+ concreteNotificaton.getAccount().getAvatar());
+ }
}
- }
- holder.setMessage(concreteNotificaton, statusListener);
- holder.setupButtons(notificationActionListener,
- concreteNotificaton.getAccount().getId(),
- concreteNotificaton.getId());
- } else {
- if (payloadForHolder instanceof List)
- for (Object item : (List) payloadForHolder) {
- if (StatusBaseViewHolder.Key.KEY_CREATED.equals(item) && statusViewData != null) {
- holder.setCreatedAt(statusViewData.getCreatedAt());
+ holder.setMessage(concreteNotificaton, statusListener);
+ holder.setupButtons(
+ notificationActionListener,
+ concreteNotificaton.getAccount().getId(),
+ concreteNotificaton.getId());
+ } else {
+ if (payloadForHolder instanceof List)
+ for (Object item : (List) payloadForHolder) {
+ if (StatusBaseViewHolder.Key.KEY_CREATED.equals(item)
+ && statusViewData != null) {
+ holder.setCreatedAt(statusViewData.getCreatedAt());
+ }
}
- }
+ }
+ break;
}
- break;
- }
- case VIEW_TYPE_FOLLOW: {
- if (payloadForHolder == null) {
- FollowViewHolder holder = (FollowViewHolder) viewHolder;
- holder.setMessage(concreteNotificaton.getAccount(), null);
- holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId());
+ case VIEW_TYPE_FOLLOW:
+ {
+ if (payloadForHolder == null) {
+ FollowViewHolder holder = (FollowViewHolder) viewHolder;
+ holder.setMessage(concreteNotificaton.getAccount(), null);
+ holder.setupButtons(
+ notificationActionListener,
+ concreteNotificaton.getAccount().getId());
+ }
+ break;
}
- break;
- }
- case VIEW_TYPE_MOVE: {
- if (payloadForHolder == null) {
- FollowViewHolder holder = (FollowViewHolder) viewHolder;
- holder.setMessage(concreteNotificaton.getTarget(), concreteNotificaton.getAccount());
- holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId());
+ case VIEW_TYPE_MOVE:
+ {
+ if (payloadForHolder == null) {
+ FollowViewHolder holder = (FollowViewHolder) viewHolder;
+ holder.setMessage(
+ concreteNotificaton.getTarget(),
+ concreteNotificaton.getAccount());
+ holder.setupButtons(
+ notificationActionListener,
+ concreteNotificaton.getAccount().getId());
+ }
+ break;
}
- break;
- }
- case VIEW_TYPE_FOLLOW_REQUEST: {
- if (payloadForHolder == null) {
- FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
- holder.setupWithAccount(concreteNotificaton.getAccount());
- holder.setupActionListener(accountActionListener);
+ case VIEW_TYPE_FOLLOW_REQUEST:
+ {
+ if (payloadForHolder == null) {
+ FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
+ holder.setupWithAccount(concreteNotificaton.getAccount());
+ holder.setupActionListener(accountActionListener);
+ }
}
- }
default:
}
}
@@ -274,17 +298,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
public void setMediaPreviewEnabled(boolean mediaPreviewEnabled) {
- this.statusDisplayOptions = statusDisplayOptions.copy(
- statusDisplayOptions.animateAvatars(),
- mediaPreviewEnabled,
- statusDisplayOptions.useAbsoluteTime(),
- statusDisplayOptions.showBotOverlay(),
- statusDisplayOptions.useBlurhash(),
- CardViewMode.NONE,
- statusDisplayOptions.confirmReblogs(),
- statusDisplayOptions.renderStatusAsMention(),
- statusDisplayOptions.hideStats()
- );
+ this.statusDisplayOptions =
+ statusDisplayOptions.copy(
+ statusDisplayOptions.animateAvatars(),
+ mediaPreviewEnabled,
+ statusDisplayOptions.useAbsoluteTime(),
+ statusDisplayOptions.showBotOverlay(),
+ statusDisplayOptions.useBlurhash(),
+ CardViewMode.NONE,
+ statusDisplayOptions.confirmReblogs(),
+ statusDisplayOptions.renderStatusAsMention(),
+ statusDisplayOptions.hideStats());
}
public boolean isMediaPreviewEnabled() {
@@ -298,43 +322,49 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification);
switch (concrete.getType()) {
case MENTION:
- case POLL: {
- if (concrete.getStatusViewData() != null && concrete.getStatusViewData().isMuted())
- return VIEW_TYPE_MUTED_STATUS;
- return VIEW_TYPE_STATUS;
- }
+ case POLL:
+ {
+ if (concrete.getStatusViewData() != null
+ && concrete.getStatusViewData().isMuted())
+ return VIEW_TYPE_MUTED_STATUS;
+ return VIEW_TYPE_STATUS;
+ }
case STATUS:
if (statusDisplayOptions.renderStatusAsMention()) {
- if (concrete.getStatusViewData() != null && concrete.getStatusViewData().isMuted())
+ if (concrete.getStatusViewData() != null
+ && concrete.getStatusViewData().isMuted())
return VIEW_TYPE_MUTED_STATUS;
return VIEW_TYPE_STATUS;
}
/* fallthrough */
case FAVOURITE:
case REBLOG:
- case EMOJI_REACTION: {
- return VIEW_TYPE_STATUS_NOTIFICATION;
- }
- case FOLLOW: {
- return VIEW_TYPE_FOLLOW;
- }
- case FOLLOW_REQUEST: {
- return VIEW_TYPE_FOLLOW_REQUEST;
- }
- case MOVE: {
- return VIEW_TYPE_MOVE;
- }
- default: {
- return VIEW_TYPE_UNKNOWN;
- }
+ case EMOJI_REACTION:
+ {
+ return VIEW_TYPE_STATUS_NOTIFICATION;
+ }
+ case FOLLOW:
+ {
+ return VIEW_TYPE_FOLLOW;
+ }
+ case FOLLOW_REQUEST:
+ {
+ return VIEW_TYPE_FOLLOW_REQUEST;
+ }
+ case MOVE:
+ {
+ return VIEW_TYPE_MOVE;
+ }
+ default:
+ {
+ return VIEW_TYPE_UNKNOWN;
+ }
}
} else if (notification instanceof NotificationViewData.Placeholder) {
return VIEW_TYPE_PLACEHOLDER;
} else {
throw new AssertionError("Unknown notification type");
}
-
-
}
public interface NotificationActionListener {
@@ -345,11 +375,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
void onExpandedChange(boolean expanded, int position);
/**
- * Called when the status {@link android.widget.ToggleButton} responsible for collapsing long
- * status content is interacted with.
+ * Called when the status {@link android.widget.ToggleButton} responsible for collapsing
+ * long status content is interacted with.
*
* @param isCollapsed Whether the status content is shown in a collapsed state or fully.
- * @param position The position of the status in the list.
+ * @param position The position of the status in the list.
*/
void onNotificationContentCollapsedChange(boolean isCollapsed, int position);
@@ -379,17 +409,19 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
Drawable drawable;
CharSequence emojifiedMessage;
- if(from != null) {
+ if (from != null) {
String format = context.getString(R.string.notification_move_format);
String wrappedFromName = StringUtils.unicodeWrap(from.getName());
String wholeMessage = String.format(format, wrappedFromName);
- emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, from.getEmojis(), message, true);
+ emojifiedMessage =
+ CustomEmojiHelper.emojify(wholeMessage, from.getEmojis(), message, true);
drawable = ContextCompat.getDrawable(context, R.drawable.ic_reply_24dp);
} else {
String format = context.getString(R.string.notification_follow_format);
String wholeMessage = String.format(format, wrappedDisplayName);
- emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message, true);
+ emojifiedMessage =
+ CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message, true);
drawable = ContextCompat.getDrawable(context, R.drawable.ic_person_add_24dp);
}
@@ -398,19 +430,26 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
message.setText(emojifiedMessage);
- String username = context.getString(R.string.status_username_format, account.getUsername());
+ String username =
+ context.getString(R.string.status_username_format, account.getUsername());
usernameView.setText(username);
- CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify(wrappedDisplayName, account.getEmojis(), usernameView, true);
+ CharSequence emojifiedDisplayName =
+ CustomEmojiHelper.emojify(
+ wrappedDisplayName, account.getEmojis(), usernameView, true);
displayNameView.setText(emojifiedDisplayName);
- int avatarRadius = avatar.getContext().getResources()
- .getDimensionPixelSize(R.dimen.avatar_radius_42dp);
+ int avatarRadius =
+ avatar.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_42dp);
- ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius,
+ ImageLoadingHelper.loadAvatar(
+ account.getAvatar(),
+ avatar,
+ avatarRadius,
statusDisplayOptions.animateAvatars());
-
}
void setupButtons(final NotificationActionListener listener, final String accountId) {
@@ -418,7 +457,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
}
-
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private final TextView message;
@@ -432,7 +470,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private final TextView replyInfo;
private final TextView contentWarningDescriptionTextView;
private final Button contentWarningButton;
- private final Button contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
+ private final Button
+ contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
private StatusDisplayOptions statusDisplayOptions;
private String accountId;
@@ -457,7 +496,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
replyInfo = itemView.findViewById(R.id.notification_reply_info);
- contentWarningDescriptionTextView = itemView.findViewById(R.id.notification_content_warning_description);
+ contentWarningDescriptionTextView =
+ itemView.findViewById(R.id.notification_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_notification_content);
this.statusDisplayOptions = statusDisplayOptions;
@@ -472,9 +512,18 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
- this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
- this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
- this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
+ this.avatarRadius48dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
+ this.avatarRadius36dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_36dp);
+ this.avatarRadius24dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_24dp);
}
private void showNotificationContent(boolean show) {
@@ -520,10 +569,15 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (createdAt != null) {
long then = createdAt.getTime();
long now = new Date().getTime();
- readout = TimestampUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
- readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
- android.text.format.DateUtils.SECOND_IN_MILLIS,
- android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
+ readout =
+ TimestampUtils.getRelativeTimeSpanString(
+ timestampInfo.getContext(), then, now);
+ readoutAloud =
+ android.text.format.DateUtils.getRelativeTimeSpanString(
+ then,
+ now,
+ android.text.format.DateUtils.SECOND_IN_MILLIS,
+ android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
} else {
// unknown minutes~
readout = "?m";
@@ -537,7 +591,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener) {
this.statusViewData = notificationViewData.getStatusViewData();
- String displayName = StringUtils.unicodeWrap(notificationViewData.getAccount().getName());
+ String displayName =
+ StringUtils.unicodeWrap(notificationViewData.getAccount().getName());
Notification.Type type = notificationViewData.getType();
Context context = message.getContext();
@@ -545,73 +600,91 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
SpannableStringBuilder builder = new SpannableStringBuilder();
switch (type) {
default:
- case FAVOURITE: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_star_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_orange), PorterDuff.Mode.SRC_ATOP);
- }
+ case FAVOURITE:
+ {
+ icon = ContextCompat.getDrawable(context, R.drawable.ic_star_24dp);
+ if (icon != null) {
+ icon.setColorFilter(
+ ContextCompat.getColor(context, R.color.tusky_orange),
+ PorterDuff.Mode.SRC_ATOP);
+ }
- String format = context.getString(R.string.notification_favourite_format);
- builder.append(String.format(format, displayName));
- break;
- }
- case REBLOG: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_blue), PorterDuff.Mode.SRC_ATOP);
+ String format = context.getString(R.string.notification_favourite_format);
+ builder.append(String.format(format, displayName));
+ break;
}
+ case REBLOG:
+ {
+ icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp);
+ if (icon != null) {
+ icon.setColorFilter(
+ ContextCompat.getColor(context, R.color.tusky_blue),
+ PorterDuff.Mode.SRC_ATOP);
+ }
- String format = context.getString(R.string.notification_reblog_format);
- builder.append(String.format(format, displayName));
- break;
- }
- case STATUS: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_home_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_blue), PorterDuff.Mode.SRC_ATOP);
+ String format = context.getString(R.string.notification_reblog_format);
+ builder.append(String.format(format, displayName));
+ break;
}
+ case STATUS:
+ {
+ icon = ContextCompat.getDrawable(context, R.drawable.ic_home_24dp);
+ if (icon != null) {
+ icon.setColorFilter(
+ ContextCompat.getColor(context, R.color.tusky_blue),
+ PorterDuff.Mode.SRC_ATOP);
+ }
- String format = context.getString(R.string.notification_subscription_format);
- builder.append(String.format(format, displayName));
- break;
- }
- case EMOJI_REACTION: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_emoji_24dp);
- if(icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_green), PorterDuff.Mode.SRC_ATOP);
+ String format =
+ context.getString(R.string.notification_subscription_format);
+ builder.append(String.format(format, displayName));
+ break;
}
+ case EMOJI_REACTION:
+ {
+ icon = ContextCompat.getDrawable(context, R.drawable.ic_emoji_24dp);
+ if (icon != null) {
+ icon.setColorFilter(
+ ContextCompat.getColor(context, R.color.tusky_green),
+ PorterDuff.Mode.SRC_ATOP);
+ }
- String format = context.getString(R.string.notification_emoji_format);
- String emojiCode = notificationViewData.getEmoji();
- builder.append(String.format(format, displayName, emojiCode));
-
- final String emojiUrl = notificationViewData.getEmojiUrl();
- if(emojiUrl != null) {
- // terrible hack... ideally, there should be a CharSequence formatter
- final int emojiPosition = format.indexOf("%s", 1)
- - "%s".length() + displayName.length();
+ String format = context.getString(R.string.notification_emoji_format);
+ String emojiCode = notificationViewData.getEmoji();
+ builder.append(String.format(format, displayName, emojiCode));
+
+ final String emojiUrl = notificationViewData.getEmojiUrl();
+ if (emojiUrl != null) {
+ // terrible hack... ideally, there should be a CharSequence formatter
+ final int emojiPosition =
+ format.indexOf("%s", 1) - "%s".length() + displayName.length();
+
+ var span = CustomEmojiHelper.createEmojiSpan(emojiUrl, message, true);
+ builder.setSpan(
+ span,
+ emojiPosition,
+ emojiPosition + emojiCode.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
- var span = CustomEmojiHelper.createEmojiSpan(emojiUrl, message, true);
- builder.setSpan(span, emojiPosition, emojiPosition + emojiCode.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ break;
}
-
- break;
- }
}
message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
- builder.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
+ builder.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ 0,
+ displayName.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- CharSequence emojifiedText = CustomEmojiHelper.emojify(builder, notificationViewData.getAccount().getEmojis(), message, true);
+ CharSequence emojifiedText =
+ CustomEmojiHelper.emojify(
+ builder, notificationViewData.getAccount().getEmojis(), message, true);
message.setText(emojifiedText);
if (statusViewData != null) {
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
- contentWarningDescriptionTextView.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
+ contentWarningDescriptionTextView.setVisibility(
+ hasSpoiler ? View.VISIBLE : View.GONE);
contentWarningButton.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
if (statusViewData.isExpanded()) {
contentWarningButton.setText(R.string.status_content_warning_show_less);
@@ -619,21 +692,25 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
contentWarningButton.setText(R.string.status_content_warning_show_more);
}
- contentWarningButton.setOnClickListener(view -> {
- if (getAdapterPosition() != RecyclerView.NO_POSITION) {
- notificationActionListener.onExpandedChange(!statusViewData.isExpanded(), getAdapterPosition());
- }
- statusContent.setVisibility(statusViewData.isExpanded() ? View.GONE : View.VISIBLE);
- });
+ contentWarningButton.setOnClickListener(
+ view -> {
+ if (getAdapterPosition() != RecyclerView.NO_POSITION) {
+ notificationActionListener.onExpandedChange(
+ !statusViewData.isExpanded(), getAdapterPosition());
+ }
+ statusContent.setVisibility(
+ statusViewData.isExpanded() ? View.GONE : View.VISIBLE);
+ });
setupContentAndSpoiler(listener);
setupReplyInfo();
}
-
}
- void setupButtons(final NotificationActionListener listener, final String accountId,
- final String notificationId) {
+ void setupButtons(
+ final NotificationActionListener listener,
+ final String accountId,
+ final String notificationId) {
this.notificationActionListener = listener;
this.accountId = accountId;
this.notificationId = notificationId;
@@ -642,8 +719,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
void setAvatar(@Nullable String statusAvatarUrl, boolean isBot) {
statusAvatar.setPaddingRelative(0, 0, 0, 0);
- ImageLoadingHelper.loadAvatar(statusAvatarUrl,
- statusAvatar, avatarRadius48dp, statusDisplayOptions.animateAvatars());
+ ImageLoadingHelper.loadAvatar(
+ statusAvatarUrl,
+ statusAvatar,
+ avatarRadius48dp,
+ statusDisplayOptions.animateAvatars());
if (statusDisplayOptions.showBotOverlay() && isBot) {
notificationAvatar.setVisibility(View.VISIBLE);
@@ -661,12 +741,18 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
int padding = Utils.dpToPx(statusAvatar.getContext(), 12);
statusAvatar.setPaddingRelative(0, 0, padding, padding);
- ImageLoadingHelper.loadAvatar(statusAvatarUrl,
- statusAvatar, avatarRadius36dp, statusDisplayOptions.animateAvatars());
+ ImageLoadingHelper.loadAvatar(
+ statusAvatarUrl,
+ statusAvatar,
+ avatarRadius36dp,
+ statusDisplayOptions.animateAvatars());
notificationAvatar.setVisibility(View.VISIBLE);
- ImageLoadingHelper.loadAvatar(notificationAvatarUrl, notificationAvatar,
- avatarRadius24dp, statusDisplayOptions.animateAvatars());
+ ImageLoadingHelper.loadAvatar(
+ notificationAvatarUrl,
+ notificationAvatar,
+ avatarRadius24dp,
+ statusDisplayOptions.animateAvatars());
}
@Override
@@ -697,12 +783,15 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
List<Emoji> emojis = statusViewData.getStatusEmojis();
if (statusViewData.isCollapsible() && (statusViewData.isExpanded() || !hasSpoiler)) {
- contentCollapseButton.setOnClickListener(view -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION && notificationActionListener != null) {
- notificationActionListener.onNotificationContentCollapsedChange(!statusViewData.isCollapsed(), position);
- }
- });
+ contentCollapseButton.setOnClickListener(
+ view -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION
+ && notificationActionListener != null) {
+ notificationActionListener.onNotificationContentCollapsedChange(
+ !statusViewData.isCollapsed(), position);
+ }
+ });
contentCollapseButton.setVisibility(View.VISIBLE);
if (statusViewData.isCollapsed()) {
@@ -718,10 +807,14 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, statusContent);
- LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener);
+ LinkHelper.setClickableText(
+ statusContent, emojifiedText, statusViewData.getMentions(), listener);
CharSequence emojifiedContentWarning =
- CustomEmojiHelper.emojify(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView);
+ CustomEmojiHelper.emojify(
+ statusViewData.getSpoilerText(),
+ statusViewData.getStatusEmojis(),
+ contentWarningDescriptionTextView);
contentWarningDescriptionTextView.setText(emojifiedContentWarning);
}
@@ -729,13 +822,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (statusViewData.getInReplyToId() != null) {
Context context = replyInfo.getContext();
String replyToAccount = statusViewData.getInReplyToAccountAcct();
- replyInfo.setText(context.getString(R.string.status_replied_to_format, replyToAccount));
+ replyInfo.setText(
+ context.getString(R.string.status_replied_to_format, replyToAccount));
if (!statusViewData.getParentVisible()) {
- replyInfo.setPaintFlags(replyInfo.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ replyInfo.setPaintFlags(
+ replyInfo.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
replyInfo.setOnClickListener(null);
} else {
- replyInfo.setPaintFlags(replyInfo.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
- replyInfo.setOnClickListener(v -> notificationActionListener.onViewReplyTo(getAdapterPosition()));
+ replyInfo.setPaintFlags(
+ replyInfo.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
+ replyInfo.setOnClickListener(
+ v -> notificationActionListener.onViewReplyTo(getAdapterPosition()));
}
replyInfo.setVisibility(View.VISIBLE);
} else {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.java
index 403fc7d..ef5991a 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.java
@@ -15,11 +15,10 @@
package com.keylesspalace.tusky.adapter;
-import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
-
+import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.ChatActionListener;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
@@ -44,17 +43,19 @@ public final class PlaceholderViewHolder extends RecyclerView.ViewHolder {
public void setup(final StatusActionListener listener, boolean progress) {
setup(progress);
- loadMoreButton.setOnClickListener(v -> {
- loadMoreButton.setEnabled(false);
- listener.onLoadMore(getAdapterPosition());
- });
+ loadMoreButton.setOnClickListener(
+ v -> {
+ loadMoreButton.setEnabled(false);
+ listener.onLoadMore(getAdapterPosition());
+ });
}
public void setup(final ChatActionListener listener, boolean progress) {
setup(progress);
- loadMoreButton.setOnClickListener( v -> {
- loadMoreButton.setEnabled(false);
- listener.onLoadMore(getAdapterPosition());
- });
+ loadMoreButton.setOnClickListener(
+ v -> {
+ loadMoreButton.setEnabled(false);
+ listener.onLoadMore(getAdapterPosition());
+ });
}
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SavedTootAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SavedTootAdapter.java
index 6d4889c..28a745b 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SavedTootAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SavedTootAdapter.java
@@ -16,17 +16,15 @@
package com.keylesspalace.tusky.adapter;
import android.content.Context;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
-
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.db.TootEntity;
-
import java.util.ArrayList;
import java.util.List;
@@ -42,8 +40,9 @@ public class SavedTootAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_saved_toot, parent, false);
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_saved_toot, parent, false);
return new TootViewHolder(view);
}
@@ -111,10 +110,11 @@ public class SavedTootAdapter extends RecyclerView.Adapter {
if (item != null) {
content.setText(item.getText());
- suppr.setOnClickListener(v -> {
- v.setEnabled(false);
- handler.delete(getAdapterPosition(), item);
- });
+ suppr.setOnClickListener(
+ v -> {
+ v.setEnabled(false);
+ handler.delete(getAdapterPosition(), item);
+ });
view.setOnClickListener(v -> handler.click(getAdapterPosition(), item));
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SingleViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SingleViewHolder.java
index 26dfbb6..4c050a7 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SingleViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/SingleViewHolder.java
@@ -1,11 +1,11 @@
package com.keylesspalace.tusky.adapter;
-import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
+import androidx.recyclerview.widget.RecyclerView;
// empty class to be able to instantiate ViewHolder which is abstract for dumbass reason
public class SingleViewHolder extends RecyclerView.ViewHolder {
- public SingleViewHolder(View view) {
- super(view);
- }
-}
+ public SingleViewHolder(View view) {
+ super(view);
+ }
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index 67c10c1..ec3132b 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -1,24 +1,24 @@
package com.keylesspalace.tusky.adapter;
+import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
+
import android.content.Context;
+import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.MotionEvent;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
-import android.util.Log;
-import android.graphics.Paint;
-
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -27,42 +27,37 @@ import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-
+import at.connyduck.sparkbutton.SparkButton;
+import at.connyduck.sparkbutton.helpers.Utils;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
-import com.google.android.material.button.MaterialButton;
import com.google.android.flexbox.FlexboxLayoutManager;
+import com.google.android.material.button.MaterialButton;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Attachment.Focus;
import com.keylesspalace.tusky.entity.Attachment.MetaData;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Emoji;
-import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.EmojiReaction;
+import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.*;
-import com.keylesspalace.tusky.view.MediaPreviewImageView;
import com.keylesspalace.tusky.view.EmojiKeyboard;
+import com.keylesspalace.tusky.view.MediaPreviewImageView;
import com.keylesspalace.tusky.viewdata.PollOptionViewData;
import com.keylesspalace.tusky.viewdata.PollViewData;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-
-import at.connyduck.sparkbutton.SparkButton;
-import at.connyduck.sparkbutton.helpers.Utils;
import kotlin.collections.CollectionsKt;
-import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
-
public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
public static class Key {
public static final String KEY_CREATED = "created";
@@ -144,26 +139,29 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
itemView.findViewById(R.id.status_media_preview_container).setClipToOutline(true);
- mediaPreviews = new MediaPreviewImageView[]{
- itemView.findViewById(R.id.status_media_preview_0),
- itemView.findViewById(R.id.status_media_preview_1),
- itemView.findViewById(R.id.status_media_preview_2),
- itemView.findViewById(R.id.status_media_preview_3)
- };
- mediaOverlays = new ImageView[]{
- itemView.findViewById(R.id.status_media_overlay_0),
- itemView.findViewById(R.id.status_media_overlay_1),
- itemView.findViewById(R.id.status_media_overlay_2),
- itemView.findViewById(R.id.status_media_overlay_3)
- };
+ mediaPreviews =
+ new MediaPreviewImageView[] {
+ itemView.findViewById(R.id.status_media_preview_0),
+ itemView.findViewById(R.id.status_media_preview_1),
+ itemView.findViewById(R.id.status_media_preview_2),
+ itemView.findViewById(R.id.status_media_preview_3)
+ };
+ mediaOverlays =
+ new ImageView[] {
+ itemView.findViewById(R.id.status_media_overlay_0),
+ itemView.findViewById(R.id.status_media_overlay_1),
+ itemView.findViewById(R.id.status_media_overlay_2),
+ itemView.findViewById(R.id.status_media_overlay_3)
+ };
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
sensitiveMediaShow = itemView.findViewById(R.id.status_sensitive_media_button);
- mediaLabels = new TextView[]{
- itemView.findViewById(R.id.status_media_label_0),
- itemView.findViewById(R.id.status_media_label_1),
- itemView.findViewById(R.id.status_media_label_2),
- itemView.findViewById(R.id.status_media_label_3)
- };
+ mediaLabels =
+ new TextView[] {
+ itemView.findViewById(R.id.status_media_label_0),
+ itemView.findViewById(R.id.status_media_label_1),
+ itemView.findViewById(R.id.status_media_label_2),
+ itemView.findViewById(R.id.status_media_label_3)
+ };
mediaDescriptions = new CharSequence[mediaLabels.length];
contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
@@ -188,17 +186,29 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
this.shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
this.longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
- this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
- this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
- this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
-
- mediaPreviewUnloaded = new ColorDrawable(ThemeUtils.getColor(itemView.getContext(), R.attr.colorBackgroundAccent));
+ this.avatarRadius48dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
+ this.avatarRadius36dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_36dp);
+ this.avatarRadius24dp =
+ itemView.getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_24dp);
+
+ mediaPreviewUnloaded =
+ new ColorDrawable(
+ ThemeUtils.getColor(itemView.getContext(), R.attr.colorBackgroundAccent));
}
protected abstract int getMediaPreviewHeight(Context context);
protected void setDisplayName(String name, List<Emoji> customEmojis) {
- CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName, true);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(name, customEmojis, displayName, true);
displayName.setText(emojifiedName);
}
@@ -212,35 +222,62 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
contentWarningButton.performClick();
}
- protected void setSpoilerAndContent(boolean expanded,
- @NonNull Spanned content,
- @Nullable String spoilerText,
- @Nullable Status.Mention[] mentions,
- @NonNull List<Emoji> emojis,
- @Nullable PollViewData poll,
- @NonNull StatusDisplayOptions statusDisplayOptions,
- final StatusActionListener listener) {
+ protected void setSpoilerAndContent(
+ boolean expanded,
+ @NonNull Spanned content,
+ @Nullable String spoilerText,
+ @Nullable Status.Mention[] mentions,
+ @NonNull List<Emoji> emojis,
+ @Nullable PollViewData poll,
+ @NonNull StatusDisplayOptions statusDisplayOptions,
+ final StatusActionListener listener) {
boolean sensitive = !TextUtils.isEmpty(spoilerText);
if (sensitive) {
- CharSequence emojiSpoiler = CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription);
+ CharSequence emojiSpoiler =
+ CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription);
contentWarningDescription.setText(emojiSpoiler);
contentWarningDescription.setVisibility(View.VISIBLE);
contentWarningButton.setVisibility(View.VISIBLE);
setContentWarningButtonText(expanded);
- contentWarningButton.setOnClickListener(view -> {
- contentWarningDescription.invalidate();
- if (getAdapterPosition() != RecyclerView.NO_POSITION) {
- listener.onExpandedChange(!expanded, getAdapterPosition());
- }
- setContentWarningButtonText(!expanded);
-
- this.setTextVisible(sensitive, !expanded, content, mentions, emojis, poll, statusDisplayOptions, listener);
- });
- this.setTextVisible(sensitive, expanded, content, mentions, emojis, poll, statusDisplayOptions, listener);
+ contentWarningButton.setOnClickListener(
+ view -> {
+ contentWarningDescription.invalidate();
+ if (getAdapterPosition() != RecyclerView.NO_POSITION) {
+ listener.onExpandedChange(!expanded, getAdapterPosition());
+ }
+ setContentWarningButtonText(!expanded);
+
+ this.setTextVisible(
+ sensitive,
+ !expanded,
+ content,
+ mentions,
+ emojis,
+ poll,
+ statusDisplayOptions,
+ listener);
+ });
+ this.setTextVisible(
+ sensitive,
+ expanded,
+ content,
+ mentions,
+ emojis,
+ poll,
+ statusDisplayOptions,
+ listener);
} else {
contentWarningDescription.setVisibility(View.GONE);
contentWarningButton.setVisibility(View.GONE);
- this.setTextVisible(sensitive, true, content, mentions, emojis, poll, statusDisplayOptions, listener);
+ this.setTextVisible(
+ sensitive,
+ true,
+ content,
+ mentions,
+ emojis,
+ poll,
+ statusDisplayOptions,
+ listener);
}
}
@@ -252,14 +289,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- private void setTextVisible(boolean sensitive,
- boolean expanded,
- Spanned content,
- Status.Mention[] mentions,
- List<Emoji> emojis,
- @Nullable PollViewData poll,
- StatusDisplayOptions statusDisplayOptions,
- final StatusActionListener listener) {
+ private void setTextVisible(
+ boolean sensitive,
+ boolean expanded,
+ Spanned content,
+ Status.Mention[] mentions,
+ List<Emoji> emojis,
+ @Nullable PollViewData poll,
+ StatusDisplayOptions statusDisplayOptions,
+ final StatusActionListener listener) {
if (expanded) {
CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content);
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
@@ -288,10 +326,11 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
pollOptions.setVisibility(View.GONE);
}
- private void setAvatar(String url,
- @Nullable String rebloggedUrl,
- boolean isBot,
- StatusDisplayOptions statusDisplayOptions) {
+ private void setAvatar(
+ String url,
+ @Nullable String rebloggedUrl,
+ boolean isBot,
+ StatusDisplayOptions statusDisplayOptions) {
int avatarRadius;
if (TextUtils.isEmpty(rebloggedUrl)) {
@@ -300,9 +339,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (statusDisplayOptions.showBotOverlay() && isBot) {
avatarInset.setVisibility(View.VISIBLE);
avatarInset.setBackgroundColor(0x50ffffff);
- Glide.with(avatarInset)
- .load(R.drawable.ic_bot_24dp)
- .into(avatarInset);
+ Glide.with(avatarInset).load(R.drawable.ic_bot_24dp).into(avatarInset);
} else {
avatarInset.setVisibility(View.GONE);
@@ -316,18 +353,21 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
avatarInset.setVisibility(View.VISIBLE);
avatarInset.setBackground(null);
- ImageLoadingHelper.loadAvatar(rebloggedUrl, avatarInset, avatarRadius24dp,
+ ImageLoadingHelper.loadAvatar(
+ rebloggedUrl,
+ avatarInset,
+ avatarRadius24dp,
statusDisplayOptions.animateAvatars());
avatarRadius = avatarRadius36dp;
}
- ImageLoadingHelper.loadAvatar(url, avatar, avatarRadius,
- statusDisplayOptions.animateAvatars());
-
+ ImageLoadingHelper.loadAvatar(
+ url, avatar, avatarRadius, statusDisplayOptions.animateAvatars());
}
- protected void setCreatedAt(Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
+ protected void setCreatedAt(
+ Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
String timestampText;
if (statusDisplayOptions.useAbsoluteTime()) {
timestampText = getAbsoluteTime(createdAt);
@@ -337,14 +377,19 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
- String readout = TimestampUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
+ String readout =
+ TimestampUtils.getRelativeTimeSpanString(
+ timestampInfo.getContext(), then, now);
timestampText = readout;
}
}
if (editedAt != null) {
- timestampText = timestampInfo.getContext()
- .getString(R.string.status_timestamp_with_edited_indicator, timestampText);
+ timestampText =
+ timestampInfo
+ .getContext()
+ .getString(
+ R.string.status_timestamp_with_edited_indicator, timestampText);
}
timestampInfo.setText(timestampText);
}
@@ -360,8 +405,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- private CharSequence getCreatedAtDescription(Date createdAt,
- StatusDisplayOptions statusDisplayOptions) {
+ private CharSequence getCreatedAtDescription(
+ Date createdAt, StatusDisplayOptions statusDisplayOptions) {
if (statusDisplayOptions.useAbsoluteTime()) {
return getAbsoluteTime(createdAt);
} else {
@@ -373,9 +418,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
- return DateUtils.getRelativeTimeSpanString(then, now,
- DateUtils.SECOND_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE);
+ return DateUtils.getRelativeTimeSpanString(
+ then, now, DateUtils.SECOND_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
}
}
}
@@ -386,7 +430,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
replyButton.setImageResource(R.drawable.ic_reply_24dp);
}
-
}
protected void setReplyInfo(StatusViewData.Concrete status, StatusActionListener listener) {
@@ -451,20 +494,18 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return ImageLoadingHelper.decodeBlurHash(this.avatar.getContext(), blurhash);
}
- private void loadImage(MediaPreviewImageView imageView,
- @Nullable String previewUrl,
- @Nullable MetaData meta,
- @Nullable String blurhash) {
+ private void loadImage(
+ MediaPreviewImageView imageView,
+ @Nullable String previewUrl,
+ @Nullable MetaData meta,
+ @Nullable String blurhash) {
Drawable placeholder = blurhash != null ? decodeBlurHash(blurhash) : mediaPreviewUnloaded;
if (TextUtils.isEmpty(previewUrl)) {
imageView.removeFocalPoint();
- Glide.with(imageView)
- .load(placeholder)
- .centerInside()
- .into(imageView);
+ Glide.with(imageView).load(placeholder).centerInside().into(imageView);
} else {
Focus focus = meta != null ? meta.getFocus() : null;
@@ -490,13 +531,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- protected void setMediaPreviews(final List<Attachment> attachments, boolean sensitive,
- final StatusActionListener listener, boolean showingContent,
- boolean useBlurhash) {
+ protected void setMediaPreviews(
+ final List<Attachment> attachments,
+ boolean sensitive,
+ final StatusActionListener listener,
+ boolean showingContent,
+ boolean useBlurhash) {
Context context = itemView.getContext();
final int n = Math.min(attachments.size(), Status.MAX_MEDIA_ATTACHMENTS);
-
final int mediaPreviewHeight = getMediaPreviewHeight(context);
if (n <= 2) {
@@ -518,8 +561,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
imageView.setVisibility(View.VISIBLE);
if (TextUtils.isEmpty(description)) {
- imageView.setContentDescription(imageView.getContext()
- .getString(R.string.action_view_media));
+ imageView.setContentDescription(
+ imageView.getContext().getString(R.string.action_view_media));
} else {
imageView.setContentDescription(description);
}
@@ -528,8 +571,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
imageView,
showingContent ? previewUrl : null,
attachment.getMeta(),
- useBlurhash ? attachment.getBlurhash() : null
- );
+ useBlurhash ? attachment.getBlurhash() : null);
final Attachment.Type type = attachment.getType();
if (showingContent && (type == Attachment.Type.VIDEO || type == Attachment.Type.GIFV)) {
@@ -549,21 +591,22 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
- sensitiveMediaShow.setOnClickListener(v -> {
- if (getAdapterPosition() != RecyclerView.NO_POSITION) {
- listener.onContentHiddenChange(false, getAdapterPosition());
- }
- v.setVisibility(View.GONE);
- sensitiveMediaWarning.setVisibility(View.VISIBLE);
- });
- sensitiveMediaWarning.setOnClickListener(v -> {
- if (getAdapterPosition() != RecyclerView.NO_POSITION) {
- listener.onContentHiddenChange(true, getAdapterPosition());
- }
- v.setVisibility(View.GONE);
- sensitiveMediaShow.setVisibility(View.VISIBLE);
- });
-
+ sensitiveMediaShow.setOnClickListener(
+ v -> {
+ if (getAdapterPosition() != RecyclerView.NO_POSITION) {
+ listener.onContentHiddenChange(false, getAdapterPosition());
+ }
+ v.setVisibility(View.GONE);
+ sensitiveMediaWarning.setVisibility(View.VISIBLE);
+ });
+ sensitiveMediaWarning.setOnClickListener(
+ v -> {
+ if (getAdapterPosition() != RecyclerView.NO_POSITION) {
+ listener.onContentHiddenChange(true, getAdapterPosition());
+ }
+ v.setVisibility(View.GONE);
+ sensitiveMediaShow.setVisibility(View.VISIBLE);
+ });
// Hide any of the placeholder previews beyond the ones set.
for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
@@ -588,14 +631,18 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private void updateMediaLabel(int index, boolean sensitive, boolean showingContent) {
Context context = itemView.getContext();
- CharSequence label = (sensitive && !showingContent) ?
- context.getString(R.string.status_sensitive_media_title) :
- mediaDescriptions[index];
+ CharSequence label =
+ (sensitive && !showingContent)
+ ? context.getString(R.string.status_sensitive_media_title)
+ : mediaDescriptions[index];
mediaLabels[index].setText(label);
}
- protected void setMediaLabel(List<Attachment> attachments, boolean sensitive,
- final StatusActionListener listener, boolean showingContent) {
+ protected void setMediaLabel(
+ List<Attachment> attachments,
+ boolean sensitive,
+ final StatusActionListener listener,
+ boolean showingContent) {
Context context = itemView.getContext();
for (int i = 0; i < mediaLabels.length; i++) {
TextView mediaLabel = mediaLabels[i];
@@ -616,32 +663,43 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- private void setAttachmentClickListener(View view, StatusActionListener listener,
- int index, Attachment attachment, boolean animateTransition) {
- view.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- if (sensitiveMediaWarning.getVisibility() == View.VISIBLE) {
- listener.onContentHiddenChange(true, getAdapterPosition());
- } else {
- listener.onViewMedia(position, index, animateTransition ? v : null);
- }
- }
- });
- view.setOnLongClickListener(v -> {
- CharSequence description = getAttachmentDescription(view.getContext(), attachment);
- Toast.makeText(view.getContext(), description, Toast.LENGTH_LONG).show();
- return true;
- });
+ private void setAttachmentClickListener(
+ View view,
+ StatusActionListener listener,
+ int index,
+ Attachment attachment,
+ boolean animateTransition) {
+ view.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ if (sensitiveMediaWarning.getVisibility() == View.VISIBLE) {
+ listener.onContentHiddenChange(true, getAdapterPosition());
+ } else {
+ listener.onViewMedia(position, index, animateTransition ? v : null);
+ }
+ }
+ });
+ view.setOnLongClickListener(
+ v -> {
+ CharSequence description =
+ getAttachmentDescription(view.getContext(), attachment);
+ Toast.makeText(view.getContext(), description, Toast.LENGTH_LONG).show();
+ return true;
+ });
}
private static CharSequence getAttachmentDescription(Context context, Attachment attachment) {
String duration = "";
- if (attachment.getMeta() != null && attachment.getMeta().getDuration() != null && attachment.getMeta().getDuration() > 0) {
+ if (attachment.getMeta() != null
+ && attachment.getMeta().getDuration() != null
+ && attachment.getMeta().getDuration() > 0) {
duration = formatDuration(attachment.getMeta().getDuration()) + " ";
}
if (TextUtils.isEmpty(attachment.getDescription())) {
- return duration + context.getString(R.string.description_status_media_no_description_placeholder);
+ return duration
+ + context.getString(
+ R.string.description_status_media_no_description_placeholder);
} else {
return duration + attachment.getDescription();
}
@@ -652,122 +710,144 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
sensitiveMediaShow.setVisibility(View.GONE);
}
- protected void setupButtons(final StatusActionListener listener,
- final String accountId,
- final String statusContent,
- StatusDisplayOptions statusDisplayOptions) {
- View.OnClickListener profileButtonClickListener = button -> {
- listener.onViewAccount(accountId);
- };
+ protected void setupButtons(
+ final StatusActionListener listener,
+ final String accountId,
+ final String statusContent,
+ StatusDisplayOptions statusDisplayOptions) {
+ View.OnClickListener profileButtonClickListener =
+ button -> {
+ listener.onViewAccount(accountId);
+ };
avatar.setOnClickListener(profileButtonClickListener);
displayName.setOnClickListener(profileButtonClickListener);
- replyButton.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onReply(position);
- }
- });
- if (reblogButton != null) {
- reblogButton.setEventListener((button, buttonState) -> {
- // return true to play animaion
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- if (statusDisplayOptions.confirmReblogs()) {
- showConfirmReblogDialog(listener, statusContent, buttonState, position);
- return false;
- } else {
- listener.onReblog(!buttonState, position);
- return true;
+ replyButton.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onReply(position);
}
- } else {
- return false;
- }
- });
+ });
+ if (reblogButton != null) {
+ reblogButton.setEventListener(
+ (button, buttonState) -> {
+ // return true to play animaion
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ if (statusDisplayOptions.confirmReblogs()) {
+ showConfirmReblogDialog(
+ listener, statusContent, buttonState, position);
+ return false;
+ } else {
+ listener.onReblog(!buttonState, position);
+ return true;
+ }
+ } else {
+ return false;
+ }
+ });
}
- favouriteButton.setEventListener((button, buttonState) -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onFavourite(!buttonState, position);
- }
- return true;
- });
+ favouriteButton.setEventListener(
+ (button, buttonState) -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onFavourite(!buttonState, position);
+ }
+ return true;
+ });
- bookmarkButton.setEventListener((button, buttonState) -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onBookmark(!buttonState, position);
- }
- return true;
- });
+ bookmarkButton.setEventListener(
+ (button, buttonState) -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onBookmark(!buttonState, position);
+ }
+ return true;
+ });
- moreButton.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onMore(v, position);
- }
- });
+ moreButton.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onMore(v, position);
+ }
+ });
/* Even though the content TextView is a child of the container, it won't respond to clicks
* if it contains URLSpans without also setting its listener. The surrounding spans will
* just eat the clicks instead of deferring to the parent listener, but WILL respond to a
* listener directly on the TextView, for whatever reason. */
- View.OnClickListener viewThreadListener = v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onViewThread(position);
- }
- };
+ View.OnClickListener viewThreadListener =
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onViewThread(position);
+ }
+ };
content.setOnClickListener(viewThreadListener);
itemView.setOnClickListener(viewThreadListener);
}
- private void showConfirmReblogDialog(StatusActionListener listener,
- String statusContent,
- boolean buttonState,
- int position) {
+ private void showConfirmReblogDialog(
+ StatusActionListener listener,
+ String statusContent,
+ boolean buttonState,
+ int position) {
int okButtonTextId = buttonState ? R.string.action_unreblog : R.string.action_reblog;
new AlertDialog.Builder(reblogButton.getContext())
.setMessage(statusContent)
- .setPositiveButton(okButtonTextId, (__, ___) -> {
- listener.onReblog(!buttonState, position);
- if (!buttonState) {
- // Play animation only when it's reblog, not unreblog
- reblogButton.playAnimation();
- }
- })
+ .setPositiveButton(
+ okButtonTextId,
+ (__, ___) -> {
+ listener.onReblog(!buttonState, position);
+ if (!buttonState) {
+ // Play animation only when it's reblog, not unreblog
+ reblogButton.playAnimation();
+ }
+ })
.show();
}
- private void setEmojiReactions(@Nullable List<EmojiReaction> reactions, final StatusActionListener listener, final String statusId) {
- if(reactButton != null) {
- reactButton.setOnClickListener(v -> {
- EmojiKeyboard.show(reactButton.getContext(), statusId, EmojiKeyboard.UNICODE_MODE, (id, emoji) -> {
- listener.onEmojiReact(true, emoji, id);
- });
- });
+ private void setEmojiReactions(
+ @Nullable List<EmojiReaction> reactions,
+ final StatusActionListener listener,
+ final String statusId) {
+ if (reactButton != null) {
+ reactButton.setOnClickListener(
+ v -> {
+ EmojiKeyboard.show(
+ reactButton.getContext(),
+ statusId,
+ EmojiKeyboard.UNICODE_MODE,
+ (id, emoji) -> {
+ listener.onEmojiReact(true, emoji, id);
+ });
+ });
}
- if(emojiReactionsView != null ) {
- if(reactions != null && reactions.size() > 0) {
+ if (emojiReactionsView != null) {
+ if (reactions != null && reactions.size() > 0) {
emojiReactionsView.setVisibility(View.VISIBLE);
FlexboxLayoutManager lm = new FlexboxLayoutManager(emojiReactionsView.getContext());
emojiReactionsView.setLayoutManager(lm);
- emojiReactionsView.setAdapter(new EmojiReactionsAdapter(reactions, listener, statusId));
- emojiReactionsView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if(event.getAction() == MotionEvent.ACTION_POINTER_UP ||
- event.getAction() == MotionEvent.ACTION_UP) {
- int position = getAdapterPosition();
- if(position != RecyclerView.NO_POSITION)
- listener.onViewThread(position);
- }
- return false;
- }
- });
+ emojiReactionsView.setAdapter(
+ new EmojiReactionsAdapter(reactions, listener, statusId));
+ emojiReactionsView.setOnTouchListener(
+ new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_POINTER_UP
+ || event.getAction() == MotionEvent.ACTION_UP) {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION)
+ listener.onViewThread(position);
+ }
+ return false;
+ }
+ });
} else {
emojiReactionsView.setVisibility(View.GONE);
emojiReactionsView.setLayoutManager(null);
@@ -776,29 +856,42 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- public void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions) {
+ public void setupWithStatus(
+ StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions) {
this.setupWithStatus(status, listener, statusDisplayOptions, null);
}
- protected void setupWithStatus(StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
- @Nullable Object payloads) {
+ protected void setupWithStatus(
+ StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions,
+ @Nullable Object payloads) {
if (payloads == null) {
setDisplayName(status.getUserFullName(), status.getAccountEmojis());
setUsername(status.getNickname());
setCreatedAt(status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
setIsReply(status.getInReplyToId() != null);
setReplyInfo(status, listener);
- setAvatar(status.getAvatar(), status.getRebloggedAvatar(), status.isBot(), statusDisplayOptions);
+ setAvatar(
+ status.getAvatar(),
+ status.getRebloggedAvatar(),
+ status.isBot(),
+ statusDisplayOptions);
setReblogged(status.isReblogged());
setFavourited(status.isFavourited());
setBookmarked(status.isBookmarked());
List<Attachment> attachments = status.getAttachments();
boolean sensitive = status.isSensitive();
- if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
- setMediaPreviews(attachments, sensitive, listener, status.isShowingContent(), statusDisplayOptions.useBlurhash());
+ if (statusDisplayOptions.mediaPreviewEnabled()
+ && hasPreviewableAttachment(attachments)) {
+ setMediaPreviews(
+ attachments,
+ sensitive,
+ listener,
+ status.isShowingContent(),
+ statusDisplayOptions.useBlurhash());
if (attachments.size() == 0) {
hideSensitiveMediaWarning();
@@ -821,11 +914,22 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
setupCard(status, statusDisplayOptions.cardViewMode(), statusDisplayOptions);
}
- setupButtons(listener, status.getSenderId(), status.getContent().toString(),
+ setupButtons(
+ listener,
+ status.getSenderId(),
+ status.getContent().toString(),
statusDisplayOptions);
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
- setSpoilerAndContent(status.isExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getStatusEmojis(), status.getPoll(), statusDisplayOptions, listener);
+ setSpoilerAndContent(
+ status.isExpanded(),
+ status.getContent(),
+ status.getSpoilerText(),
+ status.getMentions(),
+ status.getStatusEmojis(),
+ status.getPoll(),
+ statusDisplayOptions,
+ listener);
setDescriptionForStatus(status, statusDisplayOptions);
@@ -841,80 +945,96 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (payloads instanceof List)
for (Object item : (List<?>) payloads) {
if (Key.KEY_CREATED.equals(item)) {
- setCreatedAt(status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
+ setCreatedAt(
+ status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
}
}
-
}
}
protected static boolean hasPreviewableAttachment(List<Attachment> attachments) {
for (Attachment attachment : attachments) {
- if (attachment.getType() == Attachment.Type.AUDIO || attachment.getType() == Attachment.Type.UNKNOWN) {
+ if (attachment.getType() == Attachment.Type.AUDIO
+ || attachment.getType() == Attachment.Type.UNKNOWN) {
return false;
}
}
return true;
}
- private void setDescriptionForStatus(@NonNull StatusViewData.Concrete status,
- StatusDisplayOptions statusDisplayOptions) {
+ private void setDescriptionForStatus(
+ @NonNull StatusViewData.Concrete status, StatusDisplayOptions statusDisplayOptions) {
Context context = itemView.getContext();
- String description = context.getString(R.string.description_status,
- status.getUserFullName(),
- getContentWarningDescription(context, status),
- (TextUtils.isEmpty(status.getSpoilerText()) || !status.isSensitive() || status.isExpanded() ? status.getContent() : ""),
- getCreatedAtDescription(status.getCreatedAt(), statusDisplayOptions),
- status.getEditedAt() != null ? context.getString(R.string.description_status_edited) : "",
- getReblogDescription(context, status),
- status.getNickname(),
- status.isReblogged() ? context.getString(R.string.description_status_reblogged) : "",
- status.isFavourited() ? context.getString(R.string.description_status_favourited) : "",
- status.isBookmarked() ? context.getString(R.string.description_status_bookmarked) : "",
- getMediaDescription(context, status),
- getVisibilityDescription(context, status.getVisibility()),
- getFavsText(context, status.getFavouritesCount()),
- getReblogsText(context, status.getReblogsCount()),
- getPollDescription(status, context, statusDisplayOptions)
- );
+ String description =
+ context.getString(
+ R.string.description_status,
+ status.getUserFullName(),
+ getContentWarningDescription(context, status),
+ (TextUtils.isEmpty(status.getSpoilerText())
+ || !status.isSensitive()
+ || status.isExpanded()
+ ? status.getContent()
+ : ""),
+ getCreatedAtDescription(status.getCreatedAt(), statusDisplayOptions),
+ status.getEditedAt() != null
+ ? context.getString(R.string.description_status_edited)
+ : "",
+ getReblogDescription(context, status),
+ status.getNickname(),
+ status.isReblogged()
+ ? context.getString(R.string.description_status_reblogged)
+ : "",
+ status.isFavourited()
+ ? context.getString(R.string.description_status_favourited)
+ : "",
+ status.isBookmarked()
+ ? context.getString(R.string.description_status_bookmarked)
+ : "",
+ getMediaDescription(context, status),
+ getVisibilityDescription(context, status.getVisibility()),
+ getFavsText(context, status.getFavouritesCount()),
+ getReblogsText(context, status.getReblogsCount()),
+ getPollDescription(status, context, statusDisplayOptions));
itemView.setContentDescription(description);
}
- private static CharSequence getReblogDescription(Context context,
- @NonNull StatusViewData.Concrete status) {
+ private static CharSequence getReblogDescription(
+ Context context, @NonNull StatusViewData.Concrete status) {
String rebloggedUsername = status.getRebloggedByUsername();
if (rebloggedUsername != null) {
- return context
- .getString(R.string.status_boosted_format, rebloggedUsername);
+ return context.getString(R.string.status_boosted_format, rebloggedUsername);
} else {
return "";
}
}
- private static CharSequence getMediaDescription(Context context,
- @NonNull StatusViewData.Concrete status) {
+ private static CharSequence getMediaDescription(
+ Context context, @NonNull StatusViewData.Concrete status) {
if (status.getAttachments().isEmpty()) {
return "";
}
- StringBuilder mediaDescriptions = CollectionsKt.fold(
- status.getAttachments(),
- new StringBuilder(),
- (builder, a) -> {
- if (a.getDescription() == null) {
- String placeholder =
- context.getString(R.string.description_status_media_no_description_placeholder);
- return builder.append(placeholder);
- } else {
- builder.append("; ");
- return builder.append(a.getDescription());
- }
- });
+ StringBuilder mediaDescriptions =
+ CollectionsKt.fold(
+ status.getAttachments(),
+ new StringBuilder(),
+ (builder, a) -> {
+ if (a.getDescription() == null) {
+ String placeholder =
+ context.getString(
+ R.string
+ .description_status_media_no_description_placeholder);
+ return builder.append(placeholder);
+ } else {
+ builder.append("; ");
+ return builder.append(a.getDescription());
+ }
+ });
return context.getString(R.string.description_status_media, mediaDescriptions);
}
- private static CharSequence getContentWarningDescription(Context context,
- @NonNull StatusViewData.Concrete status) {
+ private static CharSequence getContentWarningDescription(
+ Context context, @NonNull StatusViewData.Concrete status) {
if (!TextUtils.isEmpty(status.getSpoilerText())) {
return context.getString(R.string.description_status_cw, status.getSpoilerText());
} else {
@@ -922,7 +1042,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- private static CharSequence getVisibilityDescription(Context context, Status.Visibility visibility) {
+ private static CharSequence getVisibilityDescription(
+ Context context, Status.Visibility visibility) {
if (visibility == null) {
return "";
@@ -951,9 +1072,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return context.getString(resource);
}
- private CharSequence getPollDescription(@NonNull StatusViewData.Concrete status,
- Context context,
- StatusDisplayOptions statusDisplayOptions) {
+ private CharSequence getPollDescription(
+ @NonNull StatusViewData.Concrete status,
+ Context context,
+ StatusDisplayOptions statusDisplayOptions) {
PollViewData poll = status.getPoll();
if (poll == null) {
return "";
@@ -962,14 +1084,17 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
List<PollOptionViewData> options = poll.getOptions();
for (int i = 0; i < args.length; i++) {
if (i < options.size()) {
- int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotesCount());
+ int percent =
+ PollViewDataKt.calculatePercent(
+ options.get(i).getVotesCount(), poll.getVotesCount());
args[i] = buildDescription(options.get(i).getTitle(), percent, context);
} else {
args[i] = "";
}
}
- args[4] = getPollInfoText(System.currentTimeMillis(), poll, statusDisplayOptions,
- context);
+ args[4] =
+ getPollInfoText(
+ System.currentTimeMillis(), poll, statusDisplayOptions, context);
return context.getString(R.string.description_poll, args);
}
}
@@ -977,7 +1102,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
protected CharSequence getFavsText(Context context, int count) {
if (count > 0) {
String countString = numberFormat.format(count);
- return HtmlCompat.fromHtml(context.getResources().getQuantityString(R.plurals.favs, count, countString), HtmlCompat.FROM_HTML_MODE_LEGACY);
+ return HtmlCompat.fromHtml(
+ context.getResources().getQuantityString(R.plurals.favs, count, countString),
+ HtmlCompat.FROM_HTML_MODE_LEGACY);
} else {
return "";
}
@@ -986,18 +1113,25 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
protected CharSequence getReblogsText(Context context, int count) {
if (count > 0) {
String countString = numberFormat.format(count);
- return HtmlCompat.fromHtml(context.getResources().getQuantityString(R.plurals.reblogs, count, countString), HtmlCompat.FROM_HTML_MODE_LEGACY);
+ return HtmlCompat.fromHtml(
+ context.getResources().getQuantityString(R.plurals.reblogs, count, countString),
+ HtmlCompat.FROM_HTML_MODE_LEGACY);
} else {
return "";
}
}
- private void setupPoll(PollViewData poll, List<Emoji> emojis,
- StatusDisplayOptions statusDisplayOptions,
- StatusActionListener listener) {
+ private void setupPoll(
+ PollViewData poll,
+ List<Emoji> emojis,
+ StatusDisplayOptions statusDisplayOptions,
+ StatusActionListener listener) {
long timestamp = System.currentTimeMillis();
- boolean expired = poll.getExpired() || (poll.getExpiresAt() != null && timestamp > poll.getExpiresAt().getTime());
+ boolean expired =
+ poll.getExpired()
+ || (poll.getExpiresAt() != null
+ && timestamp > poll.getExpiresAt().getTime());
Context context = pollDescription.getContext();
@@ -1005,53 +1139,73 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (expired || poll.getVoted()) {
// no voting possible
- View.OnClickListener viewThreadListener = v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onViewThread(position);
- }
- };
- pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, PollAdapter.RESULT, viewThreadListener);
+ View.OnClickListener viewThreadListener =
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onViewThread(position);
+ }
+ };
+ pollAdapter.setup(
+ poll.getOptions(),
+ poll.getVotesCount(),
+ poll.getVotersCount(),
+ emojis,
+ PollAdapter.RESULT,
+ viewThreadListener);
pollButton.setVisibility(View.GONE);
} else {
// voting possible
- pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, null);
+ pollAdapter.setup(
+ poll.getOptions(),
+ poll.getVotesCount(),
+ poll.getVotersCount(),
+ emojis,
+ poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE,
+ null);
pollButton.setVisibility(View.VISIBLE);
- pollButton.setOnClickListener(v -> {
-
- int position = getAdapterPosition();
+ pollButton.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
+ if (position != RecyclerView.NO_POSITION) {
- List<Integer> pollResult = pollAdapter.getSelected();
-
- if (!pollResult.isEmpty()) {
- listener.onVoteInPoll(position, pollResult);
- }
- }
+ List<Integer> pollResult = pollAdapter.getSelected();
- });
+ if (!pollResult.isEmpty()) {
+ listener.onVoteInPoll(position, pollResult);
+ }
+ }
+ });
}
pollDescription.setVisibility(View.VISIBLE);
pollDescription.setText(getPollInfoText(timestamp, poll, statusDisplayOptions, context));
}
- private CharSequence getPollInfoText(long timestamp, PollViewData poll,
- StatusDisplayOptions statusDisplayOptions,
- Context context) {
+ private CharSequence getPollInfoText(
+ long timestamp,
+ PollViewData poll,
+ StatusDisplayOptions statusDisplayOptions,
+ Context context) {
String votesText;
- if(poll.getVotersCount() == null) {
+ if (poll.getVotersCount() == null) {
String voters = numberFormat.format(poll.getVotesCount());
- votesText = context.getResources().getQuantityString(R.plurals.poll_info_votes, poll.getVotesCount(), voters);
+ votesText =
+ context.getResources()
+ .getQuantityString(
+ R.plurals.poll_info_votes, poll.getVotesCount(), voters);
} else {
// TODO: Change back to getVotersCount when this class is migrated to Kotlin
String voters = numberFormat.format(poll.getVotesCount());
- votesText = context.getResources().getQuantityString(R.plurals.poll_info_people, poll.getVotesCount(), voters);
+ votesText =
+ context.getResources()
+ .getQuantityString(
+ R.plurals.poll_info_people, poll.getVotesCount(), voters);
}
CharSequence pollDurationInfo;
if (poll.getExpired()) {
@@ -1060,25 +1214,38 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return votesText;
} else {
if (statusDisplayOptions.useAbsoluteTime()) {
- pollDurationInfo = context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.getExpiresAt()));
+ pollDurationInfo =
+ context.getString(
+ R.string.poll_info_time_absolute,
+ getAbsoluteTime(poll.getExpiresAt()));
} else {
- pollDurationInfo = TimestampUtils.formatPollDuration(pollDescription.getContext(), poll.getExpiresAt().getTime(), timestamp);
+ pollDurationInfo =
+ TimestampUtils.formatPollDuration(
+ pollDescription.getContext(),
+ poll.getExpiresAt().getTime(),
+ timestamp);
}
}
- return pollDescription.getContext().getString(R.string.poll_info_format, votesText, pollDurationInfo);
+ return pollDescription
+ .getContext()
+ .getString(R.string.poll_info_format, votesText, pollDurationInfo);
}
- protected void setupCard(StatusViewData.Concrete status, CardViewMode cardViewMode, StatusDisplayOptions statusDisplayOptions) {
- if (cardViewMode != CardViewMode.NONE &&
- status.getAttachments().size() == 0 &&
- status.getCard() != null &&
- !TextUtils.isEmpty(status.getCard().getUrl()) &&
- (!status.isCollapsible() || !status.isCollapsed())) {
+ protected void setupCard(
+ StatusViewData.Concrete status,
+ CardViewMode cardViewMode,
+ StatusDisplayOptions statusDisplayOptions) {
+ if (cardViewMode != CardViewMode.NONE
+ && status.getAttachments().size() == 0
+ && status.getCard() != null
+ && !TextUtils.isEmpty(status.getCard().getUrl())
+ && (!status.isCollapsible() || !status.isCollapsed())) {
final Card card = status.getCard();
cardView.setVisibility(View.VISIBLE);
cardTitle.setText(card.getTitle());
- if (TextUtils.isEmpty(card.getDescription()) && TextUtils.isEmpty(card.getAuthorName())) {
+ if (TextUtils.isEmpty(card.getDescription())
+ && TextUtils.isEmpty(card.getAuthorName())) {
cardDescription.setVisibility(View.GONE);
} else {
cardDescription.setVisibility(View.VISIBLE);
@@ -1091,24 +1258,33 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
cardUrl.setText(card.getUrl());
- // Statuses from other activitypub sources can be marked sensitive even if there's no media,
+ // Statuses from other activitypub sources can be marked sensitive even if there's no
+ // media,
// so let's blur the preview in that case
// If media previews are disabled, show placeholder for cards as well
- if (statusDisplayOptions.mediaPreviewEnabled() && !status.isSensitive() && !TextUtils.isEmpty(card.getImage())) {
+ if (statusDisplayOptions.mediaPreviewEnabled()
+ && !status.isSensitive()
+ && !TextUtils.isEmpty(card.getImage())) {
int topLeftRadius = 0;
int topRightRadius = 0;
int bottomRightRadius = 0;
int bottomLeftRadius = 0;
- int radius = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_radius);
+ int radius =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_radius);
if (card.getWidth() > card.getHeight()) {
cardView.setOrientation(LinearLayout.VERTICAL);
- cardImage.getLayoutParams().height = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_image_vertical_height);
+ cardImage.getLayoutParams().height =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_image_vertical_height);
cardImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -1117,8 +1293,11 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
cardView.setOrientation(LinearLayout.HORIZONTAL);
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
- cardImage.getLayoutParams().width = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
+ cardImage.getLayoutParams().width =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
topLeftRadius = radius;
@@ -1130,31 +1309,43 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
builder = builder.placeholder(decodeBlurHash(card.getBlurhash()));
}
builder.transform(
- new CenterCrop(),
- new GranularRoundedCorners(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius)
- )
+ new CenterCrop(),
+ new GranularRoundedCorners(
+ topLeftRadius,
+ topRightRadius,
+ bottomRightRadius,
+ bottomLeftRadius))
.into(cardImage);
- } else if (statusDisplayOptions.useBlurhash() && !TextUtils.isEmpty(card.getBlurhash())) {
- int radius = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_radius);
+ } else if (statusDisplayOptions.useBlurhash()
+ && !TextUtils.isEmpty(card.getBlurhash())) {
+ int radius =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_radius);
cardView.setOrientation(LinearLayout.HORIZONTAL);
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
- cardImage.getLayoutParams().width = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
+ cardImage.getLayoutParams().width =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
- Glide.with(cardImage).load(decodeBlurHash(card.getBlurhash()))
+ Glide.with(cardImage)
+ .load(decodeBlurHash(card.getBlurhash()))
.transform(
- new CenterCrop(),
- new GranularRoundedCorners(radius, 0, 0, radius)
- )
+ new CenterCrop(), new GranularRoundedCorners(radius, 0, 0, radius))
.into(cardImage);
} else {
cardView.setOrientation(LinearLayout.HORIZONTAL);
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
- cardImage.getLayoutParams().width = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
+ cardImage.getLayoutParams().width =
+ cardImage
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.card_image_horizontal_width);
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
cardImage.setImageResource(R.drawable.card_image_placeholder);
@@ -1174,5 +1365,4 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return String.format("%d:%02d:%02d", hours, minutes, seconds);
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
index 5fce8d7..33ff991 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
@@ -6,30 +6,22 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
-import android.util.Log;
-
import androidx.annotation.Nullable;
-import androidx.emoji.widget.EmojiAppCompatButton;
import androidx.recyclerview.widget.RecyclerView;
-import com.google.android.flexbox.FlexboxLayoutManager;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Status;
-import com.keylesspalace.tusky.entity.EmojiReaction;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-
import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.List;
import java.util.Date;
+import java.util.List;
class StatusDetailedViewHolder extends StatusBaseViewHolder {
private TextView reblogs;
@@ -45,22 +37,25 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
@Override
protected int getMediaPreviewHeight(Context context) {
- return context.getResources().getDimensionPixelSize(R.dimen.status_detail_media_preview_height);
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.status_detail_media_preview_height);
}
@Override
- protected void setCreatedAt(Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
- DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
+ protected void setCreatedAt(
+ Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
+ DateFormat dateFormat =
+ DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
Context context = timestampInfo.getContext();
List<String> list = new ArrayList<>();
- if (createdAt != null)
- list.add(dateFormat.format(createdAt));
+ if (createdAt != null) list.add(dateFormat.format(createdAt));
if (editedAt != null)
list.add(context.getString(R.string.status_edited, dateFormat.format(editedAt)));
timestampInfo.setText(TextUtils.join(context.getString(R.string.timestamp_joiner), list));
}
- private void setReblogAndFavCount(int reblogCount, int favCount, StatusActionListener listener) {
+ private void setReblogAndFavCount(
+ int reblogCount, int favCount, StatusActionListener listener) {
if (reblogCount > 0) {
reblogs.setText(getReblogsText(reblogs.getContext(), reblogCount));
@@ -81,18 +76,20 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
infoDivider.setVisibility(View.VISIBLE);
}
- reblogs.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onShowReblogs(position);
- }
- });
- favourites.setOnClickListener(v -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onShowFavs(position);
- }
- });
+ reblogs.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onShowReblogs(position);
+ }
+ });
+ favourites.setOnClickListener(
+ v -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onShowFavs(position);
+ }
+ });
}
private void setApplication(@Nullable Status.Application app) {
@@ -109,33 +106,46 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
}
}
}
-
+
@Override
- protected void setupWithStatus(final StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
- @Nullable Object payloads) {
+ protected void setupWithStatus(
+ final StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions,
+ @Nullable Object payloads) {
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
- setupCard(status, CardViewMode.FULL_WIDTH, statusDisplayOptions); // Always show card for detailed status
+ setupCard(
+ status,
+ CardViewMode.FULL_WIDTH,
+ statusDisplayOptions); // Always show card for detailed status
if (payloads == null) {
if (!statusDisplayOptions.hideStats()) {
- setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
+ setReblogAndFavCount(
+ status.getReblogsCount(), status.getFavouritesCount(), listener);
} else {
hideQuantitativeStats();
}
setApplication(status.getApplication());
- View.OnLongClickListener longClickListener = view -> {
- TextView textView = (TextView) view;
- ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText("toot", textView.getText());
- clipboard.setPrimaryClip(clip);
-
- Toast.makeText(view.getContext(), R.string.copy_to_clipboard_success, Toast.LENGTH_SHORT).show();
-
- return true;
- };
+ View.OnLongClickListener longClickListener =
+ view -> {
+ TextView textView = (TextView) view;
+ ClipboardManager clipboard =
+ (ClipboardManager)
+ view.getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("toot", textView.getText());
+ clipboard.setPrimaryClip(clip);
+
+ Toast.makeText(
+ view.getContext(),
+ R.string.copy_to_clipboard_success,
+ Toast.LENGTH_SHORT)
+ .show();
+
+ return true;
+ };
content.setOnLongClickListener(longClickListener);
contentWarningDescription.setOnLongClickListener(longClickListener);
@@ -170,26 +180,16 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
return;
}
- final Drawable visibilityDrawable = this.timestampInfo.getContext()
- .getDrawable(visibilityIcon);
+ final Drawable visibilityDrawable =
+ this.timestampInfo.getContext().getDrawable(visibilityIcon);
if (visibilityDrawable == null) {
return;
}
final int size = (int) this.timestampInfo.getTextSize();
- visibilityDrawable.setBounds(
- 0,
- 0,
- size,
- size
- );
+ visibilityDrawable.setBounds(0, 0, size, size);
visibilityDrawable.setTint(this.timestampInfo.getCurrentTextColor());
- this.timestampInfo.setCompoundDrawables(
- visibilityDrawable,
- null,
- null,
- null
- );
+ this.timestampInfo.setCompoundDrawables(visibilityDrawable, null, null, null);
}
private void hideQuantitativeStats() {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
index 4043f90..977a19e 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
@@ -20,12 +20,11 @@ import android.text.InputFilter;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
-import android.widget.TextView;
import android.widget.ImageButton;
-
+import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
+import at.connyduck.sparkbutton.helpers.Utils;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
@@ -34,10 +33,9 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.StringUtils;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import at.connyduck.sparkbutton.helpers.Utils;
-
public class StatusViewHolder extends StatusBaseViewHolder {
- private static final InputFilter[] COLLAPSE_INPUT_FILTER = new InputFilter[]{SmartLengthInputFilter.INSTANCE};
+ private static final InputFilter[] COLLAPSE_INPUT_FILTER =
+ new InputFilter[] {SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];
private TextView statusInfo;
@@ -48,7 +46,7 @@ public class StatusViewHolder extends StatusBaseViewHolder {
super(itemView);
statusInfo = itemView.findViewById(R.id.status_info);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_content);
- toggleVisibility = itemView.findViewById(R.id.status_toggle_mute);
+ toggleVisibility = itemView.findViewById(R.id.status_toggle_mute);
}
@Override
@@ -57,10 +55,11 @@ public class StatusViewHolder extends StatusBaseViewHolder {
}
@Override
- protected void setupWithStatus(StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
- @Nullable Object payloads) {
+ protected void setupWithStatus(
+ StatusViewData.Concrete status,
+ final StatusActionListener listener,
+ StatusDisplayOptions statusDisplayOptions,
+ @Nullable Object payloads) {
if (payloads == null) {
setupCollapsedState(status, listener);
@@ -72,32 +71,36 @@ public class StatusViewHolder extends StatusBaseViewHolder {
setRebloggedByDisplayName(rebloggedByDisplayName, status);
statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition()));
}
-
- if(status.isUserMuted() || status.isThreadMuted()) {
+
+ if (status.isUserMuted() || status.isThreadMuted()) {
toggleVisibility.setVisibility(View.VISIBLE);
- toggleVisibility.setOnClickListener(v -> listener.onMute(getAdapterPosition(), true));
+ toggleVisibility.setOnClickListener(
+ v -> listener.onMute(getAdapterPosition(), true));
} else {
toggleVisibility.setVisibility(View.GONE);
}
-
}
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
-
}
- private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) {
+ private void setRebloggedByDisplayName(
+ final CharSequence name, final StatusViewData.Concrete status) {
Context context = statusInfo.getContext();
CharSequence wrappedName = StringUtils.unicodeWrap(name);
CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName);
- CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo);
+ CharSequence emojifiedText =
+ CustomEmojiHelper.emojify(
+ boostedText, status.getRebloggedByAccountEmojis(), statusInfo);
statusInfo.setText(emojifiedText);
statusInfo.setVisibility(View.VISIBLE);
}
- // don't use this on the same ViewHolder as setRebloggedByDisplayName, will cause recycling issues as paddings are changed
+ // don't use this on the same ViewHolder as setRebloggedByDisplayName, will cause recycling
+ // issues as paddings are changed
void setPollInfo(final boolean ownPoll) {
statusInfo.setText(ownPoll ? R.string.poll_ended_created : R.string.poll_ended_voted);
- statusInfo.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_poll_24dp, 0, 0, 0);
+ statusInfo.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.ic_poll_24dp, 0, 0, 0);
statusInfo.setCompoundDrawablePadding(Utils.dpToPx(statusInfo.getContext(), 10));
statusInfo.setPaddingRelative(Utils.dpToPx(statusInfo.getContext(), 28), 0, 0, 0);
statusInfo.setVisibility(View.VISIBLE);
@@ -107,14 +110,17 @@ public class StatusViewHolder extends StatusBaseViewHolder {
statusInfo.setVisibility(View.GONE);
}
- private void setupCollapsedState(final StatusViewData.Concrete status, final StatusActionListener listener) {
+ private void setupCollapsedState(
+ final StatusViewData.Concrete status, final StatusActionListener listener) {
/* input filter for TextViews have to be set before text */
- if (status.isCollapsible() && (status.isExpanded() || TextUtils.isEmpty(status.getSpoilerText()))) {
- contentCollapseButton.setOnClickListener(view -> {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION)
- listener.onContentCollapsedChange(!status.isCollapsed(), position);
- });
+ if (status.isCollapsible()
+ && (status.isExpanded() || TextUtils.isEmpty(status.getSpoilerText()))) {
+ contentCollapseButton.setOnClickListener(
+ view -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION)
+ listener.onContentCollapsedChange(!status.isCollapsed(), position);
+ });
contentCollapseButton.setVisibility(View.VISIBLE);
if (status.isCollapsed()) {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
index 0143cb4..f9eb3ec 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
@@ -18,16 +18,13 @@ package com.keylesspalace.tusky.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-
import java.util.ArrayList;
import java.util.List;
@@ -52,16 +49,20 @@ public class ThreadAdapter extends RecyclerView.Adapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_STATUS: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_status, parent, false);
- return new StatusViewHolder(view);
- }
- case VIEW_TYPE_STATUS_DETAILED: {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_status_detailed, parent, false);
- return new StatusDetailedViewHolder(view);
- }
+ case VIEW_TYPE_STATUS:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_status, parent, false);
+ return new StatusViewHolder(view);
+ }
+ case VIEW_TYPE_STATUS_DETAILED:
+ {
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_status_detailed, parent, false);
+ return new StatusDetailedViewHolder(view);
+ }
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
index f5ab042..419eba3 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
@@ -18,16 +18,13 @@ package com.keylesspalace.tusky.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-
import java.util.List;
public final class TimelineAdapter extends RecyclerView.Adapter {
@@ -46,9 +43,10 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
private StatusDisplayOptions statusDisplayOptions;
private final StatusActionListener statusListener;
- public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource,
- StatusDisplayOptions statusDisplayOptions,
- StatusActionListener statusListener) {
+ public TimelineAdapter(
+ AdapterDataSource<StatusViewData> dataSource,
+ StatusDisplayOptions statusDisplayOptions,
+ StatusActionListener statusListener) {
this.dataSource = dataSource;
this.statusDisplayOptions = statusDisplayOptions;
this.statusListener = statusListener;
@@ -59,17 +57,17 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
}
public void setMediaPreviewEnabled(boolean mediaPreviewEnabled) {
- this.statusDisplayOptions = statusDisplayOptions.copy(
- statusDisplayOptions.animateAvatars(),
- mediaPreviewEnabled,
- statusDisplayOptions.useAbsoluteTime(),
- statusDisplayOptions.showBotOverlay(),
- statusDisplayOptions.useBlurhash(),
- statusDisplayOptions.cardViewMode(),
- statusDisplayOptions.confirmReblogs(),
- statusDisplayOptions.renderStatusAsMention(),
- statusDisplayOptions.hideStats()
- );
+ this.statusDisplayOptions =
+ statusDisplayOptions.copy(
+ statusDisplayOptions.animateAvatars(),
+ mediaPreviewEnabled,
+ statusDisplayOptions.useAbsoluteTime(),
+ statusDisplayOptions.showBotOverlay(),
+ statusDisplayOptions.useBlurhash(),
+ statusDisplayOptions.cardViewMode(),
+ statusDisplayOptions.confirmReblogs(),
+ statusDisplayOptions.renderStatusAsMention(),
+ statusDisplayOptions.hideStats());
}
@NonNull
@@ -77,21 +75,27 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
switch (viewType) {
default:
- case VIEW_TYPE_STATUS: {
- View view = LayoutInflater.from(viewGroup.getContext())
- .inflate(R.layout.item_status, viewGroup, false);
- return new StatusViewHolder(view);
- }
- case VIEW_TYPE_STATUS_MUTED: {
- View view = LayoutInflater.from(viewGroup.getContext())
- .inflate(R.layout.item_status_muted, viewGroup, false);
- return new MutedStatusViewHolder(view);
- }
- case VIEW_TYPE_PLACEHOLDER: {
- View view = LayoutInflater.from(viewGroup.getContext())
- .inflate(R.layout.item_status_placeholder, viewGroup, false);
- return new PlaceholderViewHolder(view);
- }
+ case VIEW_TYPE_STATUS:
+ {
+ View view =
+ LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.item_status, viewGroup, false);
+ return new StatusViewHolder(view);
+ }
+ case VIEW_TYPE_STATUS_MUTED:
+ {
+ View view =
+ LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.item_status_muted, viewGroup, false);
+ return new MutedStatusViewHolder(view);
+ }
+ case VIEW_TYPE_PLACEHOLDER:
+ {
+ View view =
+ LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.item_status_placeholder, viewGroup, false);
+ return new PlaceholderViewHolder(view);
+ }
}
}
@@ -100,27 +104,34 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
bindViewHolder(viewHolder, position, null);
}
-
@Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
+ public void onBindViewHolder(
+ @NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
bindViewHolder(viewHolder, position, payloads);
}
- private void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads) {
+ private void bindViewHolder(
+ @NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads) {
StatusViewData status = dataSource.getItemAt(position);
if (status instanceof StatusViewData.Placeholder) {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading());
} else if (status instanceof StatusViewData.Concrete) {
- StatusViewData.Concrete concrete = (StatusViewData.Concrete)status;
- if(concrete.isMuted()) {
+ StatusViewData.Concrete concrete = (StatusViewData.Concrete) status;
+ if (concrete.isMuted()) {
MutedStatusViewHolder holder = (MutedStatusViewHolder) viewHolder;
- holder.setupWithStatus(concrete, statusListener, statusDisplayOptions,
- payloads != null && !payloads.isEmpty() ? payloads.get(0) : null);
+ holder.setupWithStatus(
+ concrete,
+ statusListener,
+ statusDisplayOptions,
+ payloads != null && !payloads.isEmpty() ? payloads.get(0) : null);
} else {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
- holder.setupWithStatus(concrete, statusListener, statusDisplayOptions,
- payloads != null && !payloads.isEmpty() ? payloads.get(0) : null);
+ holder.setupWithStatus(
+ concrete,
+ statusListener,
+ statusDisplayOptions,
+ payloads != null && !payloads.isEmpty() ? payloads.get(0) : null);
}
}
}
@@ -135,8 +146,9 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
if (dataSource.getItemAt(position) instanceof StatusViewData.Placeholder) {
return VIEW_TYPE_PLACEHOLDER;
} else {
- StatusViewData.Concrete concrete = (StatusViewData.Concrete)dataSource.getItemAt(position);
- if(concrete.isMuted()) {
+ StatusViewData.Concrete concrete =
+ (StatusViewData.Concrete) dataSource.getItemAt(position);
+ if (concrete.isMuted()) {
return VIEW_TYPE_STATUS_MUTED;
} else {
return VIEW_TYPE_STATUS;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/UnicodeEmojiAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/UnicodeEmojiAdapter.java
index 776fa14..a74ce55 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/UnicodeEmojiAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/UnicodeEmojiAdapter.java
@@ -1,129 +1,129 @@
package com.keylesspalace.tusky.adapter;
-import android.view.*;
import android.util.*;
+import android.view.*;
+import androidx.emoji.widget.EmojiAppCompatButton;
+import androidx.recyclerview.widget.*;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
-import com.google.android.flexbox.FlexboxLayoutManager;
-import androidx.viewpager2.widget.ViewPager2;
-import androidx.recyclerview.widget.*;
-import androidx.emoji.widget.EmojiAppCompatButton;
-import androidx.emoji.text.EmojiCompat;
import com.keylesspalace.tusky.R;
-import com.keylesspalace.tusky.view.EmojiKeyboard;
import com.keylesspalace.tusky.util.Emojis;
+import com.keylesspalace.tusky.view.EmojiKeyboard;
import java.util.*;
-public class UnicodeEmojiAdapter
- extends RecyclerView.Adapter<SingleViewHolder>
- implements TabLayoutMediator.TabConfigurationStrategy, EmojiKeyboard.EmojiKeyboardAdapter {
-
+public class UnicodeEmojiAdapter extends RecyclerView.Adapter<SingleViewHolder>
+ implements TabLayoutMediator.TabConfigurationStrategy, EmojiKeyboard.EmojiKeyboardAdapter {
+
private String id;
private List<String> recents;
private EmojiKeyboard.OnEmojiSelectedListener listener;
private RecyclerView recentsView;
-
- private final static float BUTTON_WIDTH_DP = 65.0f; // empirically found value :(
-
+
+ private static final float BUTTON_WIDTH_DP = 65.0f; // empirically found value :(
+
public UnicodeEmojiAdapter(String id, EmojiKeyboard.OnEmojiSelectedListener listener) {
super();
this.id = id;
this.listener = listener;
}
-
+
@Override
public void onConfigureTab(TabLayout.Tab tab, int position) {
- if(position == 0) {
+ if (position == 0) {
tab.setIcon(R.drawable.ic_access_time);
} else {
tab.setText(Emojis.EMOJIS[position - 1][0]);
}
}
-
+
@Override
public int getItemCount() {
return Emojis.EMOJIS.length + 1;
}
-
+
@Override
public void onBindViewHolder(SingleViewHolder holder, int position) {
- if(position == 0) {
- recentsView = ((RecyclerView)holder.itemView);
+ if (position == 0) {
+ recentsView = ((RecyclerView) holder.itemView);
recentsView.setAdapter(new UnicodeEmojiPageAdapter(recents, id, listener));
} else {
- ((RecyclerView)holder.itemView).setAdapter(
- new UnicodeEmojiPageAdapter(Arrays.asList(Emojis.EMOJIS[position - 1]), id, listener));
+ ((RecyclerView) holder.itemView)
+ .setAdapter(
+ new UnicodeEmojiPageAdapter(
+ Arrays.asList(Emojis.EMOJIS[position - 1]), id, listener));
}
}
-
+
@Override
public SingleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_emoji_keyboard_page, parent, false);
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_emoji_keyboard_page, parent, false);
SingleViewHolder holder = new SingleViewHolder(view);
-
+
DisplayMetrics dm = parent.getContext().getResources().getDisplayMetrics();
float wdp = dm.widthPixels / dm.density;
int rows = (int) (wdp / BUTTON_WIDTH_DP + 0.5);
-
- ((RecyclerView)view).setLayoutManager(new GridLayoutManager(view.getContext(), rows));
+
+ ((RecyclerView) view).setLayoutManager(new GridLayoutManager(view.getContext(), rows));
return holder;
}
-
+
@Override
public void onRecentsUpdate(Set<String> set) {
recents = new ArrayList<String>(set);
Collections.reverse(recents);
- if(recentsView != null)
- recentsView.getAdapter().notifyDataSetChanged();
+ if (recentsView != null) recentsView.getAdapter().notifyDataSetChanged();
}
-
- private abstract class UnicodeEmojiBasePageAdapter extends RecyclerView.Adapter<SingleViewHolder> {
+
+ private abstract class UnicodeEmojiBasePageAdapter
+ extends RecyclerView.Adapter<SingleViewHolder> {
private final EmojiKeyboard.OnEmojiSelectedListener listener;
private final String id;
-
- public UnicodeEmojiBasePageAdapter(String id, EmojiKeyboard.OnEmojiSelectedListener listener) {
+
+ public UnicodeEmojiBasePageAdapter(
+ String id, EmojiKeyboard.OnEmojiSelectedListener listener) {
this.id = id;
this.listener = listener;
}
-
- abstract public String getEmoji(int position);
-
+
+ public abstract String getEmoji(int position);
+
@Override
public void onBindViewHolder(SingleViewHolder holder, int position) {
String emoji = getEmoji(position);
- EmojiAppCompatButton btn = (EmojiAppCompatButton)holder.itemView;
-
+ EmojiAppCompatButton btn = (EmojiAppCompatButton) holder.itemView;
+
btn.setText(emoji);
btn.setOnClickListener(v -> listener.onEmojiSelected(id, emoji));
}
-
+
@Override
public SingleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_emoji_keyboard_emoji, parent, false);
+ View view =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_emoji_keyboard_emoji, parent, false);
return new SingleViewHolder(view);
}
}
-
+
private class UnicodeEmojiPageAdapter extends UnicodeEmojiBasePageAdapter {
private final List<String> emojis;
-
- public UnicodeEmojiPageAdapter(List<String> emojis, String id, EmojiKeyboard.OnEmojiSelectedListener listener) {
+
+ public UnicodeEmojiPageAdapter(
+ List<String> emojis, String id, EmojiKeyboard.OnEmojiSelectedListener listener) {
super(id, listener);
this.emojis = emojis;
}
-
+
@Override
public int getItemCount() {
return emojis.size();
}
-
+
@Override
public String getEmoji(int position) {
return emojis.get(position);
}
}
-
}
-
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/common/DownsizeImageTask.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/common/DownsizeImageTask.java
index 17196dd..5ebfc7a 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/common/DownsizeImageTask.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/common/DownsizeImageTask.java
@@ -15,15 +15,16 @@
package com.keylesspalace.tusky.components.common;
+import static com.keylesspalace.tusky.util.MediaUtilsKt.calculateInSampleSize;
+import static com.keylesspalace.tusky.util.MediaUtilsKt.getImageOrientation;
+import static com.keylesspalace.tusky.util.MediaUtilsKt.reorientBitmap;
+
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import com.keylesspalace.tusky.util.IOUtils;
-import static com.keylesspalace.tusky.util.MediaUtilsKt.calculateInSampleSize;
-import static com.keylesspalace.tusky.util.MediaUtilsKt.getImageOrientation;
-import static com.keylesspalace.tusky.util.MediaUtilsKt.reorientBitmap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -42,14 +43,13 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
private File tempFile;
/**
- * @param sizeLimit the maximum number of bytes each image can take
+ * @param sizeLimit the maximum number of bytes each image can take
* @param contentResolver to resolve the specified images' URIs
- * @param tempFile the file where the result will be stored
- * @param listener to whom the results are given
+ * @param tempFile the file where the result will be stored
+ * @param listener to whom the results are given
*/
- public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, File tempFile,
- Listener listener)
- {
+ public DownsizeImageTask(
+ int sizeLimit, ContentResolver contentResolver, File tempFile, Listener listener) {
this.sizeLimit = sizeLimit;
this.contentResolver = contentResolver;
this.tempFile = tempFile;
@@ -59,7 +59,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... uris) {
boolean result = DownsizeImageTask.resize(uris, sizeLimit, contentResolver, tempFile);
- if(isCancelled()) {
+ if (isCancelled()) {
return false;
}
return result;
@@ -67,7 +67,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected void onPostExecute(Boolean successful) {
- if(successful) {
+ if (successful) {
listener.onSuccess(tempFile);
} else {
listener.onFailure();
@@ -75,14 +75,13 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
super.onPostExecute(successful);
}
- public static boolean resize(Uri[] uris, long sizeLimit, ContentResolver contentResolver,
- File tempFile)
- {
- for(Uri uri : uris) {
+ public static boolean resize(
+ Uri[] uris, long sizeLimit, ContentResolver contentResolver, File tempFile) {
+ for (Uri uri : uris) {
InputStream inputStream;
try {
inputStream = contentResolver.openInputStream(uri);
- } catch(FileNotFoundException e) {
+ } catch (FileNotFoundException e) {
return false;
}
// Initially, just get the image dimensions.
@@ -102,37 +101,37 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
OutputStream stream;
try {
stream = new FileOutputStream(tempFile);
- } catch(FileNotFoundException e) {
+ } catch (FileNotFoundException e) {
return false;
}
try {
inputStream = contentResolver.openInputStream(uri);
- } catch(FileNotFoundException e) {
+ } catch (FileNotFoundException e) {
return false;
}
options.inSampleSize =
- calculateInSampleSize(options, scaledImageSize, scaledImageSize);
+ calculateInSampleSize(options, scaledImageSize, scaledImageSize);
options.inJustDecodeBounds = false;
Bitmap scaledBitmap;
try {
scaledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
- } catch(OutOfMemoryError error) {
+ } catch (OutOfMemoryError error) {
return false;
} finally {
IOUtils.closeQuietly(inputStream);
}
- if(scaledBitmap == null) {
+ if (scaledBitmap == null) {
return false;
}
Bitmap reorientedBitmap = reorientBitmap(scaledBitmap, orientation);
- if(reorientedBitmap == null) {
+ if (reorientedBitmap == null) {
scaledBitmap.recycle();
return false;
}
Bitmap.CompressFormat format;
/* It's not likely the user will give transparent images over the upload limit, but
* if they do, make sure the transparency is retained. */
- if(!reorientedBitmap.hasAlpha()) {
+ if (!reorientedBitmap.hasAlpha()) {
format = Bitmap.CompressFormat.JPEG;
} else {
format = Bitmap.CompressFormat.PNG;
@@ -140,14 +139,12 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
reorientedBitmap.compress(format, 85, stream);
reorientedBitmap.recycle();
scaledImageSize /= 2;
- } while(tempFile.length() > sizeLimit);
+ } while (tempFile.length() > sizeLimit);
}
return true;
}
- /**
- * Used to communicate the results of the task.
- */
+ /** Used to communicate the results of the task. */
public interface Listener {
void onSuccess(File file);
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java
index 09d7068..704a439 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java
@@ -25,7 +25,8 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
@@ -33,19 +34,11 @@ import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.HashTag;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
-
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Created by charlag on 12/11/17.
- */
-
-public class ComposeAutoCompleteAdapter extends BaseAdapter
- implements Filterable {
+/** Created by charlag on 12/11/17. */
+public class ComposeAutoCompleteAdapter extends BaseAdapter implements Filterable {
private static final int ACCOUNT_VIEW_TYPE = 1;
private static final int HASHTAG_VIEW_TYPE = 2;
private static final int EMOJI_VIEW_TYPE = 3;
@@ -85,7 +78,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
return formatUsername(((AccountResult) resultValue));
} else if (resultValue instanceof HashtagResult) {
return formatHashtag((HashtagResult) resultValue);
- } else if (resultValue instanceof EmojiResult) {
+ } else if (resultValue instanceof EmojiResult) {
return formatEmoji((EmojiResult) resultValue);
} else {
return "";
@@ -129,9 +122,11 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
case ACCOUNT_VIEW_TYPE:
AccountViewHolder accountViewHolder;
if (convertView == null) {
- view = ((LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.item_autocomplete_account, parent, false);
+ view =
+ ((LayoutInflater)
+ context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE))
+ .inflate(R.layout.item_autocomplete_account, parent, false);
}
if (view.getTag() == null) {
view.setTag(new AccountViewHolder(view));
@@ -141,35 +136,44 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
AccountResult accountResult = ((AccountResult) getItem(position));
if (accountResult != null) {
Account account = accountResult.account;
- String formattedUsername = context.getString(
- R.string.status_username_format,
- account.getUsername()
- );
+ String formattedUsername =
+ context.getString(
+ R.string.status_username_format, account.getUsername());
accountViewHolder.username.setText(formattedUsername);
- CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(),
- account.getEmojis(), accountViewHolder.displayName);
+ CharSequence emojifiedName =
+ CustomEmojiHelper.emojify(
+ account.getName(),
+ account.getEmojis(),
+ accountViewHolder.displayName);
accountViewHolder.displayName.setText(emojifiedName);
- int avatarRadius = accountViewHolder.avatar.getContext().getResources()
- .getDimensionPixelSize(R.dimen.avatar_radius_42dp);
+ int avatarRadius =
+ accountViewHolder
+ .avatar
+ .getContext()
+ .getResources()
+ .getDimensionPixelSize(R.dimen.avatar_radius_42dp);
- boolean animateAvatar = PreferenceManager.getDefaultSharedPreferences(accountViewHolder.avatar.getContext())
- .getBoolean("animateGifAvatars", false);
+ boolean animateAvatar =
+ PreferenceManager.getDefaultSharedPreferences(
+ accountViewHolder.avatar.getContext())
+ .getBoolean("animateGifAvatars", false);
ImageLoadingHelper.loadAvatar(
account.getAvatar(),
accountViewHolder.avatar,
avatarRadius,
- animateAvatar
- );
+ animateAvatar);
}
break;
case HASHTAG_VIEW_TYPE:
if (convertView == null) {
- view = ((LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.item_autocomplete_hashtag, parent, false);
+ view =
+ ((LayoutInflater)
+ context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE))
+ .inflate(R.layout.item_autocomplete_hashtag, parent, false);
}
HashtagResult result = (HashtagResult) getItem(position);
@@ -181,9 +185,11 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
case EMOJI_VIEW_TYPE:
EmojiViewHolder emojiViewHolder;
if (convertView == null) {
- view = ((LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.item_autocomplete_emoji, parent, false);
+ view =
+ ((LayoutInflater)
+ context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE))
+ .inflate(R.layout.item_autocomplete_emoji, parent, false);
}
if (view.getTag() == null) {
view.setTag(new EmojiViewHolder(view));
@@ -193,10 +199,9 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
EmojiResult emojiResult = ((EmojiResult) getItem(position));
if (emojiResult != null) {
Emoji emoji = emojiResult.emoji;
- String formattedShortcode = context.getString(
- R.string.emoji_shortcode_format,
- emoji.getShortcode()
- );
+ String formattedShortcode =
+ context.getString(
+ R.string.emoji_shortcode_format, emoji.getShortcode());
emojiViewHolder.shortcode.setText(formattedShortcode);
Glide.with(emojiViewHolder.preview)
.load(emoji.getUrl())
@@ -206,9 +211,11 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
case SEPARATOR_VIEW_TYPE:
if (convertView == null) {
- view = ((LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.item_autocomplete_divider, parent, false);
+ view =
+ ((LayoutInflater)
+ context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE))
+ .inflate(R.layout.item_autocomplete_divider, parent, false);
}
break;
default:
@@ -262,11 +269,10 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
}
public abstract static class AutocompleteResult {
- AutocompleteResult() {
- }
+ AutocompleteResult() {}
}
- public final static class AccountResult extends AutocompleteResult {
+ public static final class AccountResult extends AutocompleteResult {
public final Account account;
public AccountResult(Account account) {
@@ -274,7 +280,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
}
}
- public final static class HashtagResult extends AutocompleteResult {
+ public static final class HashtagResult extends AutocompleteResult {
private final String hashtag;
public HashtagResult(HashTag hashtag) {
@@ -282,7 +288,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
}
}
- public final static class EmojiResult extends AutocompleteResult {
+ public static final class EmojiResult extends AutocompleteResult {
private final Emoji emoji;
public EmojiResult(Emoji emoji) {
@@ -290,7 +296,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
}
}
- public final static class ResultSeparator extends AutocompleteResult {}
+ public static final class ResultSeparator extends AutocompleteResult {}
public interface AutocompletionProvider {
List<AutocompleteResult> search(String mention);
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java
index a1a99a7..d6c0b47 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java
@@ -21,19 +21,16 @@ import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
-
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.TimePickerFragment;
-
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -53,7 +50,8 @@ public class ComposeScheduleView extends ConstraintLayout {
private TextView invalidScheduleWarningView;
private Calendar scheduleDateTime;
- public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting
+ public static int MINIMUM_SCHEDULED_SECONDS =
+ 330; // Minimum is 5 minutes, pad 30 seconds for posting
public ComposeScheduleView(Context context) {
super(context);
@@ -98,9 +96,9 @@ public class ComposeScheduleView extends ConstraintLayout {
invalidScheduleWarningView.setVisibility(GONE);
} else {
Date scheduled = scheduleDateTime.getTime();
- scheduledDateTimeView.setText(String.format("%s %s",
- dateFormat.format(scheduled),
- timeFormat.format(scheduled)));
+ scheduledDateTimeView.setText(
+ String.format(
+ "%s %s", dateFormat.format(scheduled), timeFormat.format(scheduled)));
verifyScheduledTime(scheduled);
}
}
@@ -129,16 +127,16 @@ public class ComposeScheduleView extends ConstraintLayout {
public void openPickDateDialog() {
long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000;
- CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
- .setValidator(
- DateValidatorPointForward.from(yesterday))
- .build();
+ CalendarConstraints calendarConstraints =
+ new CalendarConstraints.Builder()
+ .setValidator(DateValidatorPointForward.from(yesterday))
+ .build();
initializeSuggestedTime();
- MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
- .datePicker()
- .setSelection(scheduleDateTime.getTimeInMillis())
- .setCalendarConstraints(calendarConstraints)
- .build();
+ MaterialDatePicker<Long> picker =
+ MaterialDatePicker.Builder.datePicker()
+ .setSelection(scheduleDateTime.getTimeInMillis())
+ .setCalendarConstraints(calendarConstraints)
+ .build();
picker.addOnPositiveButtonClickListener(this::onDateSet);
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "date_picker");
}
@@ -147,8 +145,11 @@ public class ComposeScheduleView extends ConstraintLayout {
TimePickerFragment picker = new TimePickerFragment();
if (scheduleDateTime != null) {
Bundle args = new Bundle();
- args.putInt(TimePickerFragment.PICKER_TIME_HOUR, scheduleDateTime.get(Calendar.HOUR_OF_DAY));
- args.putInt(TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE));
+ args.putInt(
+ TimePickerFragment.PICKER_TIME_HOUR,
+ scheduleDateTime.get(Calendar.HOUR_OF_DAY));
+ args.putInt(
+ TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE));
picker.setArguments(args);
}
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
@@ -196,7 +197,10 @@ public class ComposeScheduleView extends ConstraintLayout {
// see https://github.com/material-components/material-components-android/issues/882
newDate.setTimeZone(TimeZone.getTimeZone("UTC"));
newDate.setTimeInMillis(selection);
- scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
+ scheduleDateTime.set(
+ newDate.get(Calendar.YEAR),
+ newDate.get(Calendar.MONTH),
+ newDate.get(Calendar.DATE));
openPickTimeDialog();
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java
index 0811703..e7bddec 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java
@@ -23,14 +23,13 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;
-import android.util.AttributeSet;
-
-import com.keylesspalace.tusky.R;
+import androidx.core.content.ContextCompat;
import at.connyduck.sparkbutton.helpers.Utils;
+import com.keylesspalace.tusky.R;
public final class ProgressImageView extends AppCompatImageView {
@@ -65,8 +64,7 @@ public final class ProgressImageView extends AppCompatImageView {
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
markBgPaint.setStyle(Paint.Style.FILL);
- markBgPaint.setColor(ContextCompat.getColor(getContext(),
- R.color.tusky_grey_10));
+ markBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.tusky_grey_10));
captionDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.spellcheck);
}
@@ -81,8 +79,9 @@ public final class ProgressImageView extends AppCompatImageView {
}
public void setChecked(boolean checked) {
- this.markBgPaint.setColor(ContextCompat.getColor(getContext(),
- checked ? R.color.tusky_blue : R.color.tusky_grey_10));
+ this.markBgPaint.setColor(
+ ContextCompat.getColor(
+ getContext(), checked ? R.color.tusky_blue : R.color.tusky_grey_10));
invalidate();
}
@@ -93,10 +92,15 @@ public final class ProgressImageView extends AppCompatImageView {
float angle = (progress / 100f) * 360 - 90;
float halfWidth = getWidth() / 2.0f;
float halfHeight = getHeight() / 2.0f;
- progressRect.set(halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f, halfHeight * 1.25f);
+ progressRect.set(
+ halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f, halfHeight * 1.25f);
biggerRect.set(progressRect);
int margin = 8;
- biggerRect.set(progressRect.left - margin, progressRect.top - margin, progressRect.right + margin, progressRect.bottom + margin);
+ biggerRect.set(
+ progressRect.left - margin,
+ progressRect.top - margin,
+ progressRect.right + margin,
+ progressRect.bottom + margin);
canvas.saveLayer(biggerRect, null, Canvas.ALL_SAVE_FLAG);
if (progress != -1) {
canvas.drawOval(progressRect, circlePaint);
@@ -112,7 +116,8 @@ public final class ProgressImageView extends AppCompatImageView {
canvas.drawCircle(circleX, circleY, circleRadius, markBgPaint);
- captionDrawable.setBounds(getWidth() - circleMargin - circleRadius,
+ captionDrawable.setBounds(
+ getWidth() - circleMargin - circleRadius,
getHeight() - circleMargin - circleRadius,
getWidth() - circleMargin,
getHeight() - circleMargin);
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressTextView.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressTextView.java
index 8078f6e..38bed01 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressTextView.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressTextView.java
@@ -23,15 +23,13 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.appcompat.content.res.AppCompatResources;
-import androidx.appcompat.widget.AppCompatTextView;
-import android.util.AttributeSet;
-
-import com.keylesspalace.tusky.R;
+import androidx.core.content.ContextCompat;
import at.connyduck.sparkbutton.helpers.Utils;
+import com.keylesspalace.tusky.R;
public final class ProgressTextView extends TextView {
@@ -66,8 +64,7 @@ public final class ProgressTextView extends TextView {
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
markBgPaint.setStyle(Paint.Style.FILL);
- markBgPaint.setColor(ContextCompat.getColor(getContext(),
- R.color.tusky_grey_10));
+ markBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.tusky_grey_10));
captionDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.spellcheck);
}
@@ -77,25 +74,31 @@ public final class ProgressTextView extends TextView {
}
public void setChecked(boolean checked) {
- this.markBgPaint.setColor(ContextCompat.getColor(getContext(),
- checked ? R.color.tusky_blue : R.color.tusky_grey_10));
+ this.markBgPaint.setColor(
+ ContextCompat.getColor(
+ getContext(), checked ? R.color.tusky_blue : R.color.tusky_grey_10));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
-
+
// https://stackoverflow.com/questions/25501185/
- canvas.translate(getScrollX(), 0);
-
+ canvas.translate(getScrollX(), 0);
+
float angle = (progress / 100f) * 360 - 90;
float halfWidth = getWidth() / 2.0f;
float halfHeight = getHeight() / 2.0f;
- progressRect.set(halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f, halfHeight * 1.25f);
+ progressRect.set(
+ halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f, halfHeight * 1.25f);
biggerRect.set(progressRect);
int margin = 8;
- biggerRect.set(progressRect.left - margin, progressRect.top - margin, progressRect.right + margin, progressRect.bottom + margin);
+ biggerRect.set(
+ progressRect.left - margin,
+ progressRect.top - margin,
+ progressRect.right + margin,
+ progressRect.bottom + margin);
canvas.saveLayer(biggerRect, null, Canvas.ALL_SAVE_FLAG);
if (progress != -1) {
canvas.drawOval(progressRect, circlePaint);
@@ -111,7 +114,8 @@ public final class ProgressTextView extends TextView {
canvas.drawCircle(circleX, circleY, circleRadius, markBgPaint);
- captionDrawable.setBounds(getWidth() - circleMargin - circleRadius,
+ captionDrawable.setBounds(
+ getWidth() - circleMargin - circleRadius,
getHeight() - circleMargin - circleRadius,
getWidth() - circleMargin,
getHeight() - circleMargin);
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
index b0e5fd5..5700c5c 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
@@ -36,7 +36,7 @@ import java.util.List;
public class ConversationViewHolder extends StatusBaseViewHolder {
private static final InputFilter[] COLLAPSE_INPUT_FILTER =
- new InputFilter[]{SmartLengthInputFilter.INSTANCE};
+ new InputFilter[] {SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];
private TextView conversationNameTextView;
@@ -46,18 +46,22 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
private StatusDisplayOptions statusDisplayOptions;
private StatusActionListener listener;
- ConversationViewHolder(View itemView, StatusDisplayOptions statusDisplayOptions,
- StatusActionListener listener)
- {
+ ConversationViewHolder(
+ View itemView,
+ StatusDisplayOptions statusDisplayOptions,
+ StatusActionListener listener) {
super(itemView);
conversationNameTextView = itemView.findViewById(R.id.conversation_name);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_content);
- avatars = new ImageView[]{avatar, itemView.findViewById(R.id.status_avatar_1),
- itemView.findViewById(R.id.status_avatar_2)};
+ avatars =
+ new ImageView[] {
+ avatar,
+ itemView.findViewById(R.id.status_avatar_1),
+ itemView.findViewById(R.id.status_avatar_2)
+ };
this.statusDisplayOptions = statusDisplayOptions;
this.listener = listener;
-
}
@Override
@@ -69,8 +73,12 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
ConversationStatusEntity status = conversation.getLastStatus();
ConversationAccountEntity account = status.getAccount();
- setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(),
- status.getSpoilerText(), listener);
+ setupCollapsedState(
+ status.getCollapsible(),
+ status.getCollapsed(),
+ status.getExpanded(),
+ status.getSpoilerText(),
+ listener);
setDisplayName(account.getDisplayName(), account.getEmojis());
setUsername(account.getUsername());
@@ -80,15 +88,19 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
setBookmarked(status.getBookmarked());
List<Attachment> attachments = status.getAttachments();
boolean sensitive = status.getSensitive();
- if(statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
- setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent(),
- statusDisplayOptions.useBlurhash());
-
- if(attachments.size() == 0) {
+ if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
+ setMediaPreviews(
+ attachments,
+ sensitive,
+ listener,
+ status.getShowingHiddenContent(),
+ statusDisplayOptions.useBlurhash());
+
+ if (attachments.size() == 0) {
hideSensitiveMediaWarning();
}
// Hide the unused label.
- for(TextView mediaLabel : mediaLabels) {
+ for (TextView mediaLabel : mediaLabels) {
mediaLabel.setVisibility(View.GONE);
}
} else {
@@ -101,12 +113,18 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
hideSensitiveMediaWarning();
}
- setupButtons(listener, account.getId(), status.getContent().toString(),
- statusDisplayOptions);
+ setupButtons(
+ listener, account.getId(), status.getContent().toString(), statusDisplayOptions);
- setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(),
- status.getMentions(), status.getEmojis(), PollViewDataKt.toViewData(status.getPoll()),
- statusDisplayOptions, listener);
+ setSpoilerAndContent(
+ status.getExpanded(),
+ status.getContent(),
+ status.getSpoilerText(),
+ status.getMentions(),
+ status.getEmojis(),
+ PollViewDataKt.toViewData(status.getPoll()),
+ statusDisplayOptions,
+ listener);
setConversationName(conversation.getAccounts());
@@ -116,27 +134,37 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
private void setConversationName(List<ConversationAccountEntity> accounts) {
Context context = conversationNameTextView.getContext();
String conversationName = "";
- if(accounts.size() == 1) {
- conversationName = context.getString(R.string.conversation_1_recipients,
- accounts.get(0).getUsername());
- } else if(accounts.size() == 2) {
+ if (accounts.size() == 1) {
+ conversationName =
+ context.getString(
+ R.string.conversation_1_recipients, accounts.get(0).getUsername());
+ } else if (accounts.size() == 2) {
+ conversationName =
+ context.getString(
+ R.string.conversation_2_recipients,
+ accounts.get(0).getUsername(),
+ accounts.get(1).getUsername());
+ } else if (accounts.size() > 2) {
conversationName =
- context.getString(R.string.conversation_2_recipients, accounts.get(0).getUsername(),
- accounts.get(1).getUsername());
- } else if(accounts.size() > 2) {
- conversationName = context.getString(R.string.conversation_more_recipients,
- accounts.get(0).getUsername(), accounts.get(1).getUsername(), accounts.size() - 2);
+ context.getString(
+ R.string.conversation_more_recipients,
+ accounts.get(0).getUsername(),
+ accounts.get(1).getUsername(),
+ accounts.size() - 2);
}
conversationNameTextView.setText(conversationName);
}
private void setAvatars(List<ConversationAccountEntity> accounts) {
- for(int i = 0; i < avatars.length; i++) {
+ for (int i = 0; i < avatars.length; i++) {
ImageView avatarView = avatars[i];
- if(i < accounts.size()) {
- ImageLoadingHelper.loadAvatar(accounts.get(i).getAvatar(), avatarView,
- avatarRadius48dp, statusDisplayOptions.animateAvatars());
+ if (i < accounts.size()) {
+ ImageLoadingHelper.loadAvatar(
+ accounts.get(i).getAvatar(),
+ avatarView,
+ avatarRadius48dp,
+ statusDisplayOptions.animateAvatars());
avatarView.setVisibility(View.VISIBLE);
} else {
avatarView.setVisibility(View.GONE);
@@ -144,20 +172,24 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
}
}
- private void setupCollapsedState(boolean collapsible, boolean collapsed, boolean expanded,
- String spoilerText, final StatusActionListener listener)
- {
+ private void setupCollapsedState(
+ boolean collapsible,
+ boolean collapsed,
+ boolean expanded,
+ String spoilerText,
+ final StatusActionListener listener) {
/* input filter for TextViews have to be set before text */
- if(collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
- contentCollapseButton.setOnClickListener(view -> {
- int position = getAdapterPosition();
- if(position != RecyclerView.NO_POSITION) {
- listener.onContentCollapsedChange(!collapsed, position);
- }
- });
+ if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
+ contentCollapseButton.setOnClickListener(
+ view -> {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onContentCollapsedChange(!collapsed, position);
+ }
+ });
contentCollapseButton.setVisibility(View.VISIBLE);
- if(collapsed) {
+ if (collapsed) {
contentCollapseButton.setText(R.string.status_content_warning_show_more);
content.setFilters(COLLAPSE_INPUT_FILTER);
} else {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/husky/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
index e1540b2..a78227f 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
@@ -23,6 +23,7 @@
package com.keylesspalace.tusky.components.notifications;
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
+
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@@ -79,9 +80,7 @@ public class NotificationHelper {
private static int notificationId = 0;
- /**
- * constants used in Intents
- */
+ /** constants used in Intents */
public static final String ACCOUNT_ID = "account_id";
private static final String TAG = "NotificationHelper";
@@ -116,10 +115,9 @@ public class NotificationHelper {
public static final String KEY_CHAT_ID = "KEY_CHAT_ID";
- /**
- * notification channels used on Android O+
- **/
+ /** notification channels used on Android O+ */
public static final String CHANNEL_MENTION = "CHANNEL_MENTION";
+
public static final String CHANNEL_FOLLOW = "CHANNEL_FOLLOW";
public static final String CHANNEL_FOLLOW_REQUEST = "CHANNEL_FOLLOW_REQUEST";
public static final String CHANNEL_BOOST = "CHANNEL_BOOST";
@@ -130,41 +128,42 @@ public class NotificationHelper {
public static final String CHANNEL_SUBSCRIPTIONS = "CHANNEL_SUBSCRIPTIONS";
public static final String CHANNEL_MOVE = "CHANNEL_MOVE";
- /**
- * WorkManager Tag
- */
+ /** WorkManager Tag */
private static final String NOTIFICATION_PULL_TAG = "pullNotifications";
/**
* by setting this as false, it's possible to test legacy notification channels on newer devices
*/
// public static final boolean NOTIFICATION_USE_CHANNELS = false;
- public static final boolean NOTIFICATION_USE_CHANNELS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ public static final boolean NOTIFICATION_USE_CHANNELS =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
/**
* Takes a given Mastodon notification and either creates a new Android notification or updates
* the state of the existing notification to reflect the new interaction.
*
* @param context to access application preferences and services
- * @param body a new Mastodon notification
+ * @param body a new Mastodon notification
* @param account the account for which the notification should be shown
*/
-
- public static void make(final Context context, Notification body, AccountEntity account, boolean isFirstOfBatch) {
+ public static void make(
+ final Context context,
+ Notification body,
+ AccountEntity account,
+ boolean isFirstOfBatch) {
body = Notification.rewriteToStatusTypeIfNeeded(body, account.getAccountId());
- if(!filterNotification(account, body, context)) {
+ if (!filterNotification(account, body, context)) {
return;
}
// Pleroma extension: don't notify about seen notifications
- if(body.getPleroma() != null && body.getPleroma().getSeen()) {
+ if (body.getPleroma() != null && body.getPleroma().getSeen()) {
return;
}
- if(body.getStatus() != null &&
- (body.getStatus().isUserMuted() ||
- body.getStatus().isThreadMuted())) {
+ if (body.getStatus() != null
+ && (body.getStatus().isUserMuted() || body.getStatus().isThreadMuted())) {
return;
}
@@ -173,17 +172,17 @@ public class NotificationHelper {
try {
currentNotifications = new JSONArray(rawCurrentNotifications);
- } catch(JSONException e) {
+ } catch (JSONException e) {
currentNotifications = new JSONArray();
}
- for(int i = 0; i < currentNotifications.length(); i++) {
+ for (int i = 0; i < currentNotifications.length(); i++) {
try {
- if(currentNotifications.getString(i).equals(body.getAccount().getName())) {
+ if (currentNotifications.getString(i).equals(body.getAccount().getName())) {
currentNotifications.remove(i);
break;
}
- } catch(JSONException e) {
+ } catch (JSONException e) {
Timber.e(e);
}
}
@@ -201,63 +200,78 @@ public class NotificationHelper {
builder.setContentTitle(titleForType(context, body, account))
.setContentText(bodyForType(body, context));
- if(body.getType() == Notification.Type.MENTION || body.getType() == Notification.Type.POLL) {
- builder.setStyle(new NotificationCompat.BigTextStyle()
- .bigText(bodyForType(body, context)));
+ if (body.getType() == Notification.Type.MENTION
+ || body.getType() == Notification.Type.POLL) {
+ builder.setStyle(
+ new NotificationCompat.BigTextStyle().bigText(bodyForType(body, context)));
}
- //load the avatar synchronously
+ // load the avatar synchronously
Bitmap accountAvatar;
try {
- FutureTarget<Bitmap> target = Glide.with(context)
- .asBitmap()
- .load(body.getAccount().getAvatar())
- .transform(new RoundedCorners(20))
- .submit();
+ FutureTarget<Bitmap> target =
+ Glide.with(context)
+ .asBitmap()
+ .load(body.getAccount().getAvatar())
+ .transform(new RoundedCorners(20))
+ .submit();
accountAvatar = target.get();
- } catch(ExecutionException | InterruptedException e) {
+ } catch (ExecutionException | InterruptedException e) {
Timber.e("Error loading account avatar %s", e);
- accountAvatar = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
+ accountAvatar =
+ BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
}
builder.setLargeIcon(accountAvatar);
- // Reply to mention action; RemoteInput is available from KitKat Watch, but buttons are available from Nougat
- if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if(body.getType() == Notification.Type.MENTION) {
- RemoteInput replyRemoteInput = new RemoteInput.Builder(KEY_REPLY)
- .setLabel(context.getString(R.string.label_quick_reply))
- .build();
+ // Reply to mention action; RemoteInput is available from KitKat Watch, but buttons are
+ // available from Nougat
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (body.getType() == Notification.Type.MENTION) {
+ RemoteInput replyRemoteInput =
+ new RemoteInput.Builder(KEY_REPLY)
+ .setLabel(context.getString(R.string.label_quick_reply))
+ .build();
- PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account);
+ PendingIntent quickReplyPendingIntent =
+ getStatusReplyIntent(REPLY_ACTION, context, body, account);
NotificationCompat.Action quickReplyAction =
- new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
- context.getString(R.string.action_quick_reply), quickReplyPendingIntent)
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_reply_24dp,
+ context.getString(R.string.action_quick_reply),
+ quickReplyPendingIntent)
.addRemoteInput(replyRemoteInput)
.build();
builder.addAction(quickReplyAction);
- PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account);
+ PendingIntent composePendingIntent =
+ getStatusReplyIntent(COMPOSE_ACTION, context, body, account);
NotificationCompat.Action composeAction =
- new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
- context.getString(R.string.action_compose_shortcut), composePendingIntent)
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_reply_24dp,
+ context.getString(R.string.action_compose_shortcut),
+ composePendingIntent)
.build();
builder.addAction(composeAction);
- } else if(body.getType() == Notification.Type.CHAT_MESSAGE) {
- RemoteInput replyRemoteInput = new RemoteInput.Builder(KEY_REPLY)
- .setLabel(context.getString(R.string.label_quick_reply))
- .build();
+ } else if (body.getType() == Notification.Type.CHAT_MESSAGE) {
+ RemoteInput replyRemoteInput =
+ new RemoteInput.Builder(KEY_REPLY)
+ .setLabel(context.getString(R.string.label_quick_reply))
+ .build();
- PendingIntent quickReplyPendingIntent = getStatusReplyIntent(CHAT_REPLY_ACTION, context, body, account);
+ PendingIntent quickReplyPendingIntent =
+ getStatusReplyIntent(CHAT_REPLY_ACTION, context, body, account);
NotificationCompat.Action quickReplyAction =
- new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
- context.getString(R.string.action_quick_reply), quickReplyPendingIntent)
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_reply_24dp,
+ context.getString(R.string.action_quick_reply),
+ quickReplyPendingIntent)
.addRemoteInput(replyRemoteInput)
.build();
@@ -271,21 +285,23 @@ public class NotificationHelper {
builder.setOnlyAlertOnce(true);
// only alert for the first notification of a batch to avoid multiple alerts at once
- if(!isFirstOfBatch) {
+ if (!isFirstOfBatch) {
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
}
// Summary
// =======
- final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true);
+ final NotificationCompat.Builder summaryBuilder =
+ newNotification(context, body, account, true);
- if(currentNotifications.length() != 1) {
+ if (currentNotifications.length() != 1) {
try {
- String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
+ String title =
+ context.getString(
+ R.string.notification_title_summary, currentNotifications.length());
String text = joinNames(context, currentNotifications);
- summaryBuilder.setContentTitle(title)
- .setContentText(text);
- } catch(JSONException e) {
+ summaryBuilder.setContentTitle(title).setContentText(text);
+ } catch (JSONException e) {
Timber.e(e);
}
}
@@ -299,24 +315,26 @@ public class NotificationHelper {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(notificationId, builder.build());
- if(currentNotifications.length() == 1) {
- notificationManager.notify((int) account.getId(), builder.setGroupSummary(true).build());
+ if (currentNotifications.length() == 1) {
+ notificationManager.notify(
+ (int) account.getId(), builder.setGroupSummary(true).build());
} else {
notificationManager.notify((int) account.getId(), summaryBuilder.build());
}
}
- private static NotificationCompat.Builder newNotification(Context context, Notification body, AccountEntity account, boolean summary) {
+ private static NotificationCompat.Builder newNotification(
+ Context context, Notification body, AccountEntity account, boolean summary) {
Intent summaryResultIntent = new Intent(context, MainActivity.class);
summaryResultIntent.putExtra(ACCOUNT_ID, account.getId());
TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context);
summaryStackBuilder.addParentStack(MainActivity.class);
summaryStackBuilder.addNextIntent(summaryResultIntent);
- PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent(
- (int) (notificationId + account.getId() * 10000),
- pendingIntentFlags(false)
- );
+ PendingIntent summaryResultPendingIntent =
+ summaryStackBuilder.getPendingIntent(
+ (int) (notificationId + account.getId() * 10000),
+ pendingIntentFlags(false));
// we have to switch account here
Intent eventResultIntent = new Intent(context, MainActivity.class);
@@ -325,41 +343,50 @@ public class NotificationHelper {
eventStackBuilder.addParentStack(MainActivity.class);
eventStackBuilder.addNextIntent(eventResultIntent);
- PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent(
- (int) account.getId(),
- pendingIntentFlags(false));
+ PendingIntent eventResultPendingIntent =
+ eventStackBuilder.getPendingIntent(
+ (int) account.getId(), pendingIntentFlags(false));
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
- PendingIntent deletePendingIntent = PendingIntent.getBroadcast(
- context, summary ? (int) account.getId() : notificationId,
- deleteIntent,
- pendingIntentFlags(false));
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body))
- .setSmallIcon(R.drawable.ic_notify)
- .setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent)
- .setDeleteIntent(deletePendingIntent)
- .setColor(BuildConfig.FLAVOR == "green" ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue))
- .setGroup(account.getAccountId())
- .setAutoCancel(true)
- .setShortcutId(Long.toString(account.getId()))
- .setDefaults(0); // So it doesn't ring twice, notify only in Target callback
+ PendingIntent deletePendingIntent =
+ PendingIntent.getBroadcast(
+ context,
+ summary ? (int) account.getId() : notificationId,
+ deleteIntent,
+ pendingIntentFlags(false));
+
+ NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(context, getChannelId(account, body))
+ .setSmallIcon(R.drawable.ic_notify)
+ .setContentIntent(
+ summary ? summaryResultPendingIntent : eventResultPendingIntent)
+ .setDeleteIntent(deletePendingIntent)
+ .setColor(
+ BuildConfig.FLAVOR == "green"
+ ? Color.parseColor("#19A341")
+ : ContextCompat.getColor(context, R.color.tusky_blue))
+ .setGroup(account.getAccountId())
+ .setAutoCancel(true)
+ .setShortcutId(Long.toString(account.getId()))
+ .setDefaults(0); // So it doesn't ring twice, notify only in Target callback
setupPreferences(account, builder);
return builder;
}
- private static PendingIntent getStatusReplyIntent(String action, Context context, Notification body, AccountEntity account) {
- Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class)
- .setAction(action)
- .putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
- .putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
- .putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
- .putExtra(KEY_NOTIFICATION_ID, notificationId);
-
- if(action == CHAT_REPLY_ACTION) {
+ private static PendingIntent getStatusReplyIntent(
+ String action, Context context, Notification body, AccountEntity account) {
+ Intent replyIntent =
+ new Intent(context, SendStatusBroadcastReceiver.class)
+ .setAction(action)
+ .putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
+ .putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
+ .putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
+ .putExtra(KEY_NOTIFICATION_ID, notificationId);
+
+ if (action == CHAT_REPLY_ACTION) {
replyIntent.putExtra(KEY_CHAT_ID, body.getChatMessage().getChatId());
} else {
Status status = body.getStatus();
@@ -373,13 +400,14 @@ public class NotificationHelper {
Status.Mention[] mentions = actionableStatus.getMentions();
List<String> mentionedUsernames = new ArrayList<>();
mentionedUsernames.add(actionableStatus.getAccount().getUsername());
- for(Status.Mention mention : mentions) {
+ for (Status.Mention mention : mentions) {
mentionedUsernames.add(mention.getUsername());
}
mentionedUsernames.removeAll(Collections.singleton(account.getUsername()));
mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames));
- replyIntent.putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor)
+ replyIntent
+ .putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor)
.putExtra(KEY_CITED_TEXT, citedText)
.putExtra(KEY_CITED_STATUS_ID, inReplyToId)
.putExtra(KEY_VISIBILITY, replyVisibility)
@@ -387,63 +415,68 @@ public class NotificationHelper {
.putExtra(KEY_MENTIONS, mentionedUsernames.toArray(new String[0]));
}
- return PendingIntent.getBroadcast(context.getApplicationContext(),
+ return PendingIntent.getBroadcast(
+ context.getApplicationContext(),
notificationId,
replyIntent,
pendingIntentFlags(true));
}
- public static void createNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) {
- if(NOTIFICATION_USE_CHANNELS) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-
- String[] channelIds = new String[]{
- CHANNEL_MENTION + account.getIdentifier(),
- CHANNEL_FOLLOW + account.getIdentifier(),
- CHANNEL_FOLLOW_REQUEST + account.getIdentifier(),
- CHANNEL_BOOST + account.getIdentifier(),
- CHANNEL_FAVOURITE + account.getIdentifier(),
- CHANNEL_POLL + account.getIdentifier(),
- CHANNEL_EMOJI_REACTION + account.getIdentifier(),
- CHANNEL_CHAT_MESSAGES + account.getIdentifier(),
- CHANNEL_SUBSCRIPTIONS + account.getIdentifier(),
- CHANNEL_MOVE + account.getIdentifier()
- };
+ public static void createNotificationChannelsForAccount(
+ @NonNull AccountEntity account, @NonNull Context context) {
+ if (NOTIFICATION_USE_CHANNELS) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ String[] channelIds =
+ new String[] {
+ CHANNEL_MENTION + account.getIdentifier(),
+ CHANNEL_FOLLOW + account.getIdentifier(),
+ CHANNEL_FOLLOW_REQUEST + account.getIdentifier(),
+ CHANNEL_BOOST + account.getIdentifier(),
+ CHANNEL_FAVOURITE + account.getIdentifier(),
+ CHANNEL_POLL + account.getIdentifier(),
+ CHANNEL_EMOJI_REACTION + account.getIdentifier(),
+ CHANNEL_CHAT_MESSAGES + account.getIdentifier(),
+ CHANNEL_SUBSCRIPTIONS + account.getIdentifier(),
+ CHANNEL_MOVE + account.getIdentifier()
+ };
int[] channelNames = {
- R.string.notification_mention_name,
- R.string.notification_follow_name,
- R.string.notification_follow_request_name,
- R.string.notification_boost_name,
- R.string.notification_favourite_name,
- R.string.notification_poll_name,
- R.string.notification_emoji_name,
- R.string.notification_chat_message_name,
- R.string.notification_subscription_name,
- R.string.notification_move_name
+ R.string.notification_mention_name,
+ R.string.notification_follow_name,
+ R.string.notification_follow_request_name,
+ R.string.notification_boost_name,
+ R.string.notification_favourite_name,
+ R.string.notification_poll_name,
+ R.string.notification_emoji_name,
+ R.string.notification_chat_message_name,
+ R.string.notification_subscription_name,
+ R.string.notification_move_name
};
int[] channelDescriptions = {
- R.string.notification_mention_descriptions,
- R.string.notification_follow_description,
- R.string.notification_follow_request_description,
- R.string.notification_boost_description,
- R.string.notification_favourite_description,
- R.string.notification_poll_description,
- R.string.notification_emoji_description,
- R.string.notification_chat_message_description,
- R.string.notification_subscription_description,
- R.string.notification_move_description
+ R.string.notification_mention_descriptions,
+ R.string.notification_follow_description,
+ R.string.notification_follow_request_description,
+ R.string.notification_boost_description,
+ R.string.notification_favourite_description,
+ R.string.notification_poll_description,
+ R.string.notification_emoji_description,
+ R.string.notification_chat_message_description,
+ R.string.notification_subscription_description,
+ R.string.notification_move_description
};
List<NotificationChannel> channels = new ArrayList<>(9);
- NotificationChannelGroup channelGroup = new NotificationChannelGroup(account.getIdentifier(), account.getFullName());
+ NotificationChannelGroup channelGroup =
+ new NotificationChannelGroup(account.getIdentifier(), account.getFullName());
//noinspection ConstantConditions
notificationManager.createNotificationChannelGroup(channelGroup);
- for(int i = 0; i < channelIds.length; i++) {
+ for (int i = 0; i < channelIds.length; i++) {
String id = channelIds[i];
String name = context.getString(channelNames[i]);
String description = context.getString(channelDescriptions[i]);
@@ -463,18 +496,22 @@ public class NotificationHelper {
}
}
- public static void deleteNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) {
- if(NOTIFICATION_USE_CHANNELS) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ public static void deleteNotificationChannelsForAccount(
+ @NonNull AccountEntity account, @NonNull Context context) {
+ if (NOTIFICATION_USE_CHANNELS) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//noinspection ConstantConditions
notificationManager.deleteNotificationChannelGroup(account.getIdentifier());
}
}
- public static void deleteLegacyNotificationChannels(@NonNull Context context, @NonNull AccountManager accountManager) {
- if(NOTIFICATION_USE_CHANNELS) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ public static void deleteLegacyNotificationChannels(
+ @NonNull Context context, @NonNull AccountManager accountManager) {
+ if (NOTIFICATION_USE_CHANNELS) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// used until Tusky 1.4
//noinspection ConstantConditions
@@ -484,21 +521,24 @@ public class NotificationHelper {
notificationManager.deleteNotificationChannel(CHANNEL_FOLLOW);
// used until Tusky 1.7
- for(AccountEntity account : accountManager.getAllAccountsOrderedByActive()) {
- notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE + " " + account.getIdentifier());
+ for (AccountEntity account : accountManager.getAllAccountsOrderedByActive()) {
+ notificationManager.deleteNotificationChannel(
+ CHANNEL_FAVOURITE + " " + account.getIdentifier());
}
}
}
- public static boolean areNotificationsEnabled(@NonNull Context context, @NonNull AccountManager accountManager) {
- if(NOTIFICATION_USE_CHANNELS) {
+ public static boolean areNotificationsEnabled(
+ @NonNull Context context, @NonNull AccountManager accountManager) {
+ if (NOTIFICATION_USE_CHANNELS) {
// on Android >= O, notifications are enabled, if at least one channel is enabled
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//noinspection ConstantConditions
- if(notificationManager.areNotificationsEnabled()) {
- for(NotificationChannel channel : notificationManager.getNotificationChannels()) {
- if(channel.getImportance() > NotificationManager.IMPORTANCE_NONE) {
+ if (notificationManager.areNotificationsEnabled()) {
+ for (NotificationChannel channel : notificationManager.getNotificationChannels()) {
+ if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) {
Timber.d("NotificationsEnabled");
return true;
}
@@ -508,7 +548,8 @@ public class NotificationHelper {
return false;
} else {
- // on Android < O, notifications are enabled, if at least one account has notification enabled
+ // on Android < O, notifications are enabled, if at least one account has notification
+ // enabled
return accountManager.areNotificationsEnabled();
}
}
@@ -517,18 +558,25 @@ public class NotificationHelper {
WorkManager workManager = WorkManager.getInstance(context);
workManager.cancelAllWorkByTag(NOTIFICATION_PULL_TAG);
- WorkRequest workRequest = new PeriodicWorkRequest.Builder(
- NotificationWorker.class,
- PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS,
- PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS
- )
- .addTag(NOTIFICATION_PULL_TAG)
- .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
- .build();
+ WorkRequest workRequest =
+ new PeriodicWorkRequest.Builder(
+ NotificationWorker.class,
+ PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
+ TimeUnit.MILLISECONDS,
+ PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
+ TimeUnit.MILLISECONDS)
+ .addTag(NOTIFICATION_PULL_TAG)
+ .setConstraints(
+ new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build())
+ .build();
workManager.enqueue(workRequest);
- Timber.d("enabled notification checks with ${PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS} ms interval");
+ Timber.d(
+ "enabled notification checks with"
+ + " ${PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS} ms interval");
}
public static void disablePullNotifications(Context context) {
@@ -536,30 +584,36 @@ public class NotificationHelper {
Timber.d("Disabled notification checks");
}
- public static void clearNotificationsForActiveAccount(@NonNull Context context, @NonNull AccountManager accountManager) {
+ public static void clearNotificationsForActiveAccount(
+ @NonNull Context context, @NonNull AccountManager accountManager) {
AccountEntity account = accountManager.getActiveAccount();
- if(account != null && !account.getActiveNotifications().equals("[]")) {
- Single.fromCallable(() -> {
- account.setActiveNotifications("[]");
- accountManager.saveAccount(account);
-
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- //noinspection ConstantConditions
- notificationManager.cancel((int) account.getId());
- return true;
- })
+ if (account != null && !account.getActiveNotifications().equals("[]")) {
+ Single.fromCallable(
+ () -> {
+ account.setActiveNotifications("[]");
+ accountManager.saveAccount(account);
+
+ NotificationManager notificationManager =
+ (NotificationManager)
+ context.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ //noinspection ConstantConditions
+ notificationManager.cancel((int) account.getId());
+ return true;
+ })
.subscribeOn(Schedulers.io())
.subscribe();
}
}
- private static boolean filterNotification(AccountEntity account, Notification notification,
- Context context) {
- if(NOTIFICATION_USE_CHANNELS) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ private static boolean filterNotification(
+ AccountEntity account, Notification notification, Context context) {
+ if (NOTIFICATION_USE_CHANNELS) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
String channelId = getChannelId(account, notification);
- if(channelId == null) {
+ if (channelId == null) {
// unknown notificationtype
return false;
}
@@ -568,7 +622,7 @@ public class NotificationHelper {
return channel.getImportance() > NotificationManager.IMPORTANCE_NONE;
}
- switch(notification.getType()) {
+ switch (notification.getType()) {
case MENTION:
return account.getNotificationsMentioned();
case STATUS:
@@ -596,7 +650,7 @@ public class NotificationHelper {
@Nullable
private static String getChannelId(AccountEntity account, Notification notification) {
- switch(notification.getType()) {
+ switch (notification.getType()) {
case MENTION:
return CHANNEL_MENTION + account.getIdentifier();
case STATUS:
@@ -623,21 +677,20 @@ public class NotificationHelper {
}
private static void setupPreferences(
- AccountEntity account,
- NotificationCompat.Builder builder) {
- if(NOTIFICATION_USE_CHANNELS) {
- return; //do nothing on Android O or newer, the system uses the channel settings anyway
+ AccountEntity account, NotificationCompat.Builder builder) {
+ if (NOTIFICATION_USE_CHANNELS) {
+ return; // do nothing on Android O or newer, the system uses the channel settings anyway
}
- if(account.getNotificationSound()) {
+ if (account.getNotificationSound()) {
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
}
- if(account.getNotificationVibration()) {
- builder.setVibrate(new long[]{500, 500});
+ if (account.getNotificationVibration()) {
+ builder.setVibrate(new long[] {500, 500});
}
- if(account.getNotificationLight()) {
+ if (account.getNotificationLight()) {
builder.setLights(0xFF2B90D9, 300, 1000);
}
}
@@ -648,20 +701,23 @@ public class NotificationHelper {
@Nullable
private static String joinNames(Context context, JSONArray array) throws JSONException {
- if(array.length() > 3) {
+ if (array.length() > 3) {
int length = array.length();
- return String.format(context.getString(R.string.notification_summary_large),
+ return String.format(
+ context.getString(R.string.notification_summary_large),
wrapItemAt(array, length - 1),
wrapItemAt(array, length - 2),
wrapItemAt(array, length - 3),
length - 3);
- } else if(array.length() == 3) {
- return String.format(context.getString(R.string.notification_summary_medium),
+ } else if (array.length() == 3) {
+ return String.format(
+ context.getString(R.string.notification_summary_medium),
wrapItemAt(array, 2),
wrapItemAt(array, 1),
wrapItemAt(array, 0));
- } else if(array.length() == 2) {
- return String.format(context.getString(R.string.notification_summary_small),
+ } else if (array.length() == 2) {
+ return String.format(
+ context.getString(R.string.notification_summary_small),
wrapItemAt(array, 1),
wrapItemAt(array, 0));
}
@@ -670,49 +726,55 @@ public class NotificationHelper {
}
@Nullable
- private static String titleForType(Context context, Notification notification, AccountEntity account) {
+ private static String titleForType(
+ Context context, Notification notification, AccountEntity account) {
String accountName = StringUtils.unicodeWrap(notification.getAccount().getName());
- switch(notification.getType()) {
+ switch (notification.getType()) {
case MENTION:
- return String.format(context.getString(R.string.notification_mention_format),
- accountName);
+ return String.format(
+ context.getString(R.string.notification_mention_format), accountName);
case STATUS:
- return String.format(context.getString(R.string.notification_subscription_format),
- accountName);
+ return String.format(
+ context.getString(R.string.notification_subscription_format), accountName);
case FOLLOW:
- return String.format(context.getString(R.string.notification_follow_format),
- accountName);
+ return String.format(
+ context.getString(R.string.notification_follow_format), accountName);
case FOLLOW_REQUEST:
- return String.format(context.getString(R.string.notification_follow_request_format),
+ return String.format(
+ context.getString(R.string.notification_follow_request_format),
accountName);
case FAVOURITE:
- return String.format(context.getString(R.string.notification_favourite_format),
- accountName);
+ return String.format(
+ context.getString(R.string.notification_favourite_format), accountName);
case REBLOG:
- return String.format(context.getString(R.string.notification_reblog_format),
- accountName);
+ return String.format(
+ context.getString(R.string.notification_reblog_format), accountName);
case EMOJI_REACTION:
- return String.format(context.getString(R.string.notification_emoji_format),
- accountName, notification.getEmoji());
+ return String.format(
+ context.getString(R.string.notification_emoji_format),
+ accountName,
+ notification.getEmoji());
case POLL:
- if(notification.getStatus().getAccount().getId().equals(account.getAccountId())) {
+ if (notification.getStatus().getAccount().getId().equals(account.getAccountId())) {
return context.getString(R.string.poll_ended_created);
} else {
return context.getString(R.string.poll_ended_voted);
}
case CHAT_MESSAGE:
- return String.format(context.getString(R.string.notification_chat_message_format),
- accountName);
- case MOVE: {
- return String.format(context.getString(R.string.notification_move_format), accountName);
- }
+ return String.format(
+ context.getString(R.string.notification_chat_message_format), accountName);
+ case MOVE:
+ {
+ return String.format(
+ context.getString(R.string.notification_move_format), accountName);
+ }
}
return null;
}
private static String bodyForType(Notification notification, Context context) {
- switch(notification.getType()) {
+ switch (notification.getType()) {
case MOVE:
return "@" + notification.getTarget().getUsername();
case FOLLOW:
@@ -723,32 +785,37 @@ public class NotificationHelper {
case REBLOG:
case EMOJI_REACTION:
case STATUS:
- if(!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
+ if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
return notification.getStatus().getSpoilerText();
} else {
return notification.getStatus().getContent().toString();
}
case POLL:
- if(!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
+ if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
return notification.getStatus().getSpoilerText();
} else {
- StringBuilder builder = new StringBuilder(notification.getStatus().getContent());
+ StringBuilder builder =
+ new StringBuilder(notification.getStatus().getContent());
builder.append('\n');
Poll poll = notification.getStatus().getPoll();
- for(PollOption option : poll.getOptions()) {
- builder.append(buildDescription(option.getTitle(),
- PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount()),
- context));
+ for (PollOption option : poll.getOptions()) {
+ builder.append(
+ buildDescription(
+ option.getTitle(),
+ PollViewDataKt.calculatePercent(
+ option.getVotesCount(), poll.getVotesCount()),
+ context));
builder.append('\n');
}
return builder.toString();
}
case CHAT_MESSAGE:
- if(!TextUtils.isEmpty(notification.getChatMessage().getContent())) {
+ if (!TextUtils.isEmpty(notification.getChatMessage().getContent())) {
return notification.getChatMessage().getContent().toString();
- } else if(notification.getChatMessage().getAttachment() != null) {
- return context.getString(notification.getChatMessage().getAttachment().describeAttachmentType());
- } else if(notification.getChatMessage().getCard() != null) {
+ } else if (notification.getChatMessage().getAttachment() != null) {
+ return context.getString(
+ notification.getChatMessage().getAttachment().describeAttachmentType());
+ } else if (notification.getChatMessage().getCard() != null) {
return context.getString(R.string.link);
} else {
return "";
@@ -759,8 +826,8 @@ public class NotificationHelper {
public static int pendingIntentFlags(boolean mutable) {
return (PendingIntent.FLAG_UPDATE_CURRENT
- | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ?
- (mutable ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE) : 0)
- );
+ | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+ ? (mutable ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE)
+ : 0));
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/core/utils/image/SvgModule.java b/husky/app/src/main/java/com/keylesspalace/tusky/core/utils/image/SvgModule.java
index 08fc1b9..b07ba61 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/core/utils/image/SvgModule.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/core/utils/image/SvgModule.java
@@ -29,17 +29,13 @@ import com.bumptech.glide.module.LibraryGlideModule;
import com.caverock.androidsvg.SVG;
import java.io.InputStream;
-/**
- * Module for adding SVG decoding support to Glide.
- */
+/** Module for adding SVG decoding support to Glide. */
@GlideModule
public class SvgModule extends LibraryGlideModule {
@Override
public void registerComponents(
- @NonNull Context context,
- @NonNull Glide glide,
- @NonNull Registry registry) {
+ @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.register(SVG.class, Drawable.class, new SvgDrawableTranscoder(context))
.append(InputStream.class, SVG.class, new SvgDecoder());
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/husky/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
index e92b63e..7965789 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
@@ -20,397 +20,486 @@ import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
-
import com.keylesspalace.tusky.TabDataKt;
import com.keylesspalace.tusky.components.conversation.ConversationEntity;
-/**
- * DB version & declare DAO
- */
-
-@Database(entities = { TootEntity.class, DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
- TimelineAccountEntity.class, ConversationEntity.class, ChatEntity.class, ChatMessageEntity.class
- }, version = 28)
+/** DB version & declare DAO */
+@Database(
+ entities = {
+ TootEntity.class,
+ DraftEntity.class,
+ AccountEntity.class,
+ InstanceEntity.class,
+ TimelineStatusEntity.class,
+ TimelineAccountEntity.class,
+ ConversationEntity.class,
+ ChatEntity.class,
+ ChatMessageEntity.class
+ },
+ version = 28)
public abstract class AppDatabase extends RoomDatabase {
public abstract TootDao tootDao();
+
public abstract AccountDao accountDao();
+
public abstract InstanceDao instanceDao();
+
public abstract ConversationsDao conversationDao();
+
public abstract TimelineDao timelineDao();
+
public abstract ChatsDao chatsDao();
+
public abstract DraftDao draftDao();
- public static final Migration MIGRATION_2_3 = new Migration(2, 3) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE TootEntity2 (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, text TEXT, urls TEXT, contentWarning TEXT);");
- database.execSQL("INSERT INTO TootEntity2 SELECT * FROM TootEntity;");
- database.execSQL("DROP TABLE TootEntity;");
- database.execSQL("ALTER TABLE TootEntity2 RENAME TO TootEntity;");
- }
- };
-
- public static final Migration MIGRATION_3_4 = new Migration(3, 4) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToId TEXT");
- database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToText TEXT");
- database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToUsername TEXT");
- database.execSQL("ALTER TABLE TootEntity ADD COLUMN visibility INTEGER");
- }
- };
-
- public static final Migration MIGRATION_4_5 = new Migration(4, 5) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `AccountEntity` (" +
- "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
- "`domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, " +
- "`isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, " +
- "`username` TEXT NOT NULL, `displayName` TEXT NOT NULL, " +
- "`profilePictureUrl` TEXT NOT NULL, " +
- "`notificationsEnabled` INTEGER NOT NULL, " +
- "`notificationsMentioned` INTEGER NOT NULL, " +
- "`notificationsFollowed` INTEGER NOT NULL, " +
- "`notificationsReblogged` INTEGER NOT NULL, " +
- "`notificationsFavorited` INTEGER NOT NULL, " +
- "`notificationSound` INTEGER NOT NULL, " +
- "`notificationVibration` INTEGER NOT NULL, " +
- "`notificationLight` INTEGER NOT NULL, " +
- "`lastNotificationId` TEXT NOT NULL, " +
- "`activeNotifications` TEXT NOT NULL)");
- database.execSQL("CREATE UNIQUE INDEX `index_AccountEntity_domain_accountId` ON `AccountEntity` (`domain`, `accountId`)");
- }
- };
-
- public static final Migration MIGRATION_5_6 = new Migration(5, 6) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS `EmojiListEntity` (`instance` TEXT NOT NULL, `emojiList` TEXT NOT NULL, PRIMARY KEY(`instance`))");
- }
- };
-
- public static final Migration MIGRATION_6_7 = new Migration(6, 7) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS `InstanceEntity` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, PRIMARY KEY(`instance`))");
- database.execSQL("INSERT OR REPLACE INTO `InstanceEntity` SELECT `instance`,`emojiList`, NULL FROM `EmojiListEntity`;");
- database.execSQL("DROP TABLE `EmojiListEntity`;");
- }
- };
-
- public static final Migration MIGRATION_7_8 = new Migration(7, 8) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `emojis` TEXT NOT NULL DEFAULT '[]'");
- }
- };
-
- public static final Migration MIGRATION_8_9 = new Migration(8, 9) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `descriptions` TEXT DEFAULT '[]'");
- }
- };
-
- public static final Migration MIGRATION_9_10 = new Migration(9, 10) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultPostPrivacy` INTEGER NOT NULL DEFAULT 1");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultMediaSensitivity` INTEGER NOT NULL DEFAULT 0");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `alwaysShowSensitiveMedia` INTEGER NOT NULL DEFAULT 0");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `mediaPreviewEnabled` INTEGER NOT NULL DEFAULT '1'");
- }
- };
-
- public static final Migration MIGRATION_10_11 = new Migration(10, 11) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS `TimelineAccountEntity` (" +
- "`serverId` TEXT NOT NULL, " +
- "`timelineUserId` INTEGER NOT NULL, " +
- "`instance` TEXT NOT NULL, " +
- "`localUsername` TEXT NOT NULL, " +
- "`username` TEXT NOT NULL, " +
- "`displayName` TEXT NOT NULL, " +
- "`url` TEXT NOT NULL, " +
- "`avatar` TEXT NOT NULL, " +
- "`emojis` TEXT NOT NULL," +
- "PRIMARY KEY(`serverId`, `timelineUserId`))");
-
- database.execSQL("CREATE TABLE IF NOT EXISTS `TimelineStatusEntity` (" +
- "`serverId` TEXT NOT NULL, " +
- "`url` TEXT, " +
- "`timelineUserId` INTEGER NOT NULL, " +
- "`authorServerId` TEXT," +
- "`instance` TEXT, " +
- "`inReplyToId` TEXT, " +
- "`inReplyToAccountId` TEXT, " +
- "`content` TEXT, " +
- "`createdAt` INTEGER NOT NULL, " +
- "`emojis` TEXT, " +
- "`reblogsCount` INTEGER NOT NULL, " +
- "`favouritesCount` INTEGER NOT NULL, " +
- "`reblogged` INTEGER NOT NULL, " +
- "`favourited` INTEGER NOT NULL, " +
- "`sensitive` INTEGER NOT NULL, " +
- "`spoilerText` TEXT, " +
- "`visibility` INTEGER, " +
- "`attachments` TEXT, " +
- "`mentions` TEXT, " +
- "`application` TEXT, " +
- "`reblogServerId` TEXT, " +
- "`reblogAccountId` TEXT," +
- " PRIMARY KEY(`serverId`, `timelineUserId`)," +
- " FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) " +
- "ON UPDATE NO ACTION ON DELETE NO ACTION )");
- database.execSQL("CREATE INDEX IF NOT EXISTS" +
- "`index_TimelineStatusEntity_authorServerId_timelineUserId` " +
- "ON `TimelineStatusEntity` (`authorServerId`, `timelineUserId`)");
- }
- };
-
- public static final Migration MIGRATION_11_12 = new Migration(11, 12) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- String defaultTabs = TabDataKt.HOME + ";" +
- TabDataKt.NOTIFICATIONS + ";" +
- TabDataKt.LOCAL + ";" +
- TabDataKt.FEDERATED;
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `tabPreferences` TEXT NOT NULL DEFAULT '" + defaultTabs + "'");
-
- database.execSQL("CREATE TABLE IF NOT EXISTS `ConversationEntity` (" +
- "`accountId` INTEGER NOT NULL, " +
- "`id` TEXT NOT NULL, " +
- "`accounts` TEXT NOT NULL, " +
- "`unread` INTEGER NOT NULL, " +
- "`s_id` TEXT NOT NULL, " +
- "`s_url` TEXT, " +
- "`s_inReplyToId` TEXT, " +
- "`s_inReplyToAccountId` TEXT, " +
- "`s_account` TEXT NOT NULL, " +
- "`s_content` TEXT NOT NULL, " +
- "`s_createdAt` INTEGER NOT NULL, " +
- "`s_emojis` TEXT NOT NULL, " +
- "`s_favouritesCount` INTEGER NOT NULL, " +
- "`s_favourited` INTEGER NOT NULL, " +
- "`s_sensitive` INTEGER NOT NULL, " +
- "`s_spoilerText` TEXT NOT NULL, " +
- "`s_attachments` TEXT NOT NULL, " +
- "`s_mentions` TEXT NOT NULL, " +
- "`s_showingHiddenContent` INTEGER NOT NULL, " +
- "`s_expanded` INTEGER NOT NULL, " +
- "`s_collapsible` INTEGER NOT NULL, " +
- "`s_collapsed` INTEGER NOT NULL, " +
- "PRIMARY KEY(`id`, `accountId`))");
-
- }
- };
-
- public static final Migration MIGRATION_12_13 = new Migration(12, 13) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
-
- database.execSQL("DROP TABLE IF EXISTS `TimelineAccountEntity`");
- database.execSQL("DROP TABLE IF EXISTS `TimelineStatusEntity`");
-
- database.execSQL("CREATE TABLE IF NOT EXISTS `TimelineAccountEntity` (" +
- "`serverId` TEXT NOT NULL, " +
- "`timelineUserId` INTEGER NOT NULL, " +
- "`localUsername` TEXT NOT NULL, " +
- "`username` TEXT NOT NULL, " +
- "`displayName` TEXT NOT NULL, " +
- "`url` TEXT NOT NULL, " +
- "`avatar` TEXT NOT NULL, " +
- "`emojis` TEXT NOT NULL," +
- "PRIMARY KEY(`serverId`, `timelineUserId`))");
-
- database.execSQL("CREATE TABLE IF NOT EXISTS `TimelineStatusEntity` (" +
- "`serverId` TEXT NOT NULL, " +
- "`url` TEXT, " +
- "`timelineUserId` INTEGER NOT NULL, " +
- "`authorServerId` TEXT," +
- "`inReplyToId` TEXT, " +
- "`inReplyToAccountId` TEXT, " +
- "`content` TEXT, " +
- "`createdAt` INTEGER NOT NULL, " +
- "`emojis` TEXT, " +
- "`reblogsCount` INTEGER NOT NULL, " +
- "`favouritesCount` INTEGER NOT NULL, " +
- "`reblogged` INTEGER NOT NULL, " +
- "`favourited` INTEGER NOT NULL, " +
- "`sensitive` INTEGER NOT NULL, " +
- "`spoilerText` TEXT, " +
- "`visibility` INTEGER, " +
- "`attachments` TEXT, " +
- "`mentions` TEXT, " +
- "`application` TEXT, " +
- "`reblogServerId` TEXT, " +
- "`reblogAccountId` TEXT," +
- " PRIMARY KEY(`serverId`, `timelineUserId`)," +
- " FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) " +
- "ON UPDATE NO ACTION ON DELETE NO ACTION )");
- database.execSQL("CREATE INDEX IF NOT EXISTS" +
- "`index_TimelineStatusEntity_authorServerId_timelineUserId` " +
- "ON `TimelineStatusEntity` (`authorServerId`, `timelineUserId`)");
- }
- };
-
- public static final Migration MIGRATION_10_13 = new Migration(10, 13) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- MIGRATION_11_12.migrate(database);
- MIGRATION_12_13.migrate(database);
- }
- };
-
- public static final Migration MIGRATION_13_14 = new Migration(13, 14) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsFilter` TEXT NOT NULL DEFAULT '[]'");
- }
- };
-
- public static final Migration MIGRATION_14_15 = new Migration(14, 15) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `poll` TEXT");
- database.execSQL("ALTER TABLE `ConversationEntity` ADD COLUMN `s_poll` TEXT");
- }
- };
-
- public static final Migration MIGRATION_15_16 = new Migration(15, 16) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsPolls` INTEGER NOT NULL DEFAULT 1");
- }
- };
-
- public static final Migration MIGRATION_16_17 = new Migration(16, 17) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `TimelineAccountEntity` ADD COLUMN `bot` INTEGER NOT NULL DEFAULT 0");
- }
- };
-
- public static final Migration MIGRATION_17_18 = new Migration(17, 18) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `alwaysOpenSpoiler` INTEGER NOT NULL DEFAULT 0");
- }
- };
-
- public static final Migration MIGRATION_18_19 = new Migration(18, 19) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptions` INTEGER");
- database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptionLength` INTEGER");
-
- database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `poll` TEXT");
- }
- };
-
- public static final Migration MIGRATION_19_20 = new Migration(19, 20) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `bookmarked` INTEGER NOT NULL DEFAULT 0");
- database.execSQL("ALTER TABLE `ConversationEntity` ADD COLUMN `s_bookmarked` INTEGER NOT NULL DEFAULT 0");
- }
-
- };
-
- public static final Migration MIGRATION_20_21 = new Migration(20, 21) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `version` TEXT");
- database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `markdownMode` INTEGER");
- }
- };
-
- public static final Migration MIGRATION_21_22 = new Migration(21, 22) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsEmojiReactions` INTEGER NOT NULL DEFAULT 1");
- }
- };
-
- public static final Migration MIGRATION_22_23 = new Migration(22, 23) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- // leave markdownMode unused, we don't need it anymore but don't recreate table
- // database.execSQL("ALTER TABLE `TootEntity` DROP COLUMN `markdownMode`");
- database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `formattingSyntax` TEXT NOT NULL DEFAULT ''");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultFormattingSyntax` TEXT NOT NULL DEFAULT ''");
- }
- };
-
- public static final Migration MIGRATION_23_24 = new Migration(23, 24) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsFollowRequested` INTEGER NOT NULL DEFAULT 1");
- }
- };
-
- public static final Migration MIGRATION_24_25 = new Migration(24, 25) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `ChatEntity` (`localId` INTEGER NOT NULL," +
- "`chatId` TEXT NOT NULL," +
- "`accountId` TEXT NOT NULL," +
- "`unread` INTEGER NOT NULL," +
- "`updatedAt` INTEGER NOT NULL," +
- "`lastMessageId` TEXT," +
- "PRIMARY KEY (`localId`, `chatId`))");
- database.execSQL("CREATE TABLE `ChatMessageEntity` (`localId` INTEGER NOT NULL," +
- "`messageId` TEXT NOT NULL," +
- "`content` TEXT," +
- "`chatId` TEXT NOT NULL," +
- "`accountId` TEXT NOT NULL," +
- "`createdAt` INTEGER NOT NULL," +
- "`attachment` TEXT," +
- "`emojis` TEXT NOT NULL," +
- "PRIMARY KEY (`localId`, `messageId`))");
- database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `chatLimit` INTEGER");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsChatMessages` INTEGER NOT NULL DEFAULT 1");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsStreamingEnabled` INTEGER NOT NULL DEFAULT 1");
- database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `pleroma` TEXT");
- }
- };
-
- public static final Migration MIGRATION_25_26 = new Migration(25, 26) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsSubscriptions` INTEGER NOT NULL DEFAULT 1");
- database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsMove` INTEGER NOT NULL DEFAULT 1");
- }
- };
-
- public static final Migration MIGRATION_26_27 = new Migration(26, 27) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL(
- "CREATE TABLE IF NOT EXISTS `DraftEntity` (" +
- "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
- "`accountId` INTEGER NOT NULL, " +
- "`inReplyToId` TEXT," +
- "`content` TEXT," +
- "`contentWarning` TEXT," +
- "`sensitive` INTEGER NOT NULL," +
- "`visibility` INTEGER NOT NULL," +
- "`attachments` TEXT NOT NULL," +
- "`poll` TEXT," +
- "`formattingSyntax` TEXT NOT NULL," +
- "`failedToSend` INTEGER NOT NULL)"
- );
- }
- };
-
- public static final Migration MIGRATION_27_28 = new Migration(27, 28) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `editedAt` INTEGER");
- database.execSQL("ALTER TABLE `ConversationEntity` ADD COLUMN `s_editedAt` INTEGER");
- }
- };
+ public static final Migration MIGRATION_2_3 =
+ new Migration(2, 3) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE TootEntity2 (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT"
+ + " NULL, text TEXT, urls TEXT, contentWarning TEXT);");
+ database.execSQL("INSERT INTO TootEntity2 SELECT * FROM TootEntity;");
+ database.execSQL("DROP TABLE TootEntity;");
+ database.execSQL("ALTER TABLE TootEntity2 RENAME TO TootEntity;");
+ }
+ };
+
+ public static final Migration MIGRATION_3_4 =
+ new Migration(3, 4) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToId TEXT");
+ database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToText TEXT");
+ database.execSQL("ALTER TABLE TootEntity ADD COLUMN inReplyToUsername TEXT");
+ database.execSQL("ALTER TABLE TootEntity ADD COLUMN visibility INTEGER");
+ }
+ };
+
+ public static final Migration MIGRATION_4_5 =
+ new Migration(4, 5) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE `AccountEntity` ("
+ + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ + "`domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, "
+ + "`isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, "
+ + "`username` TEXT NOT NULL, `displayName` TEXT NOT NULL, "
+ + "`profilePictureUrl` TEXT NOT NULL, "
+ + "`notificationsEnabled` INTEGER NOT NULL, "
+ + "`notificationsMentioned` INTEGER NOT NULL, "
+ + "`notificationsFollowed` INTEGER NOT NULL, "
+ + "`notificationsReblogged` INTEGER NOT NULL, "
+ + "`notificationsFavorited` INTEGER NOT NULL, "
+ + "`notificationSound` INTEGER NOT NULL, "
+ + "`notificationVibration` INTEGER NOT NULL, "
+ + "`notificationLight` INTEGER NOT NULL, "
+ + "`lastNotificationId` TEXT NOT NULL, "
+ + "`activeNotifications` TEXT NOT NULL)");
+ database.execSQL(
+ "CREATE UNIQUE INDEX `index_AccountEntity_domain_accountId` ON"
+ + " `AccountEntity` (`domain`, `accountId`)");
+ }
+ };
+
+ public static final Migration MIGRATION_5_6 =
+ new Migration(5, 6) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `EmojiListEntity` (`instance` TEXT NOT"
+ + " NULL, `emojiList` TEXT NOT NULL, PRIMARY KEY(`instance`))");
+ }
+ };
+
+ public static final Migration MIGRATION_6_7 =
+ new Migration(6, 7) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `InstanceEntity` (`instance` TEXT NOT NULL,"
+ + " `emojiList` TEXT, `maximumTootCharacters` INTEGER, PRIMARY"
+ + " KEY(`instance`))");
+ database.execSQL(
+ "INSERT OR REPLACE INTO `InstanceEntity` SELECT `instance`,`emojiList`,"
+ + " NULL FROM `EmojiListEntity`;");
+ database.execSQL("DROP TABLE `EmojiListEntity`;");
+ }
+ };
+
+ public static final Migration MIGRATION_7_8 =
+ new Migration(7, 8) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `emojis` TEXT NOT NULL DEFAULT"
+ + " '[]'");
+ }
+ };
+
+ public static final Migration MIGRATION_8_9 =
+ new Migration(8, 9) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `TootEntity` ADD COLUMN `descriptions` TEXT DEFAULT '[]'");
+ }
+ };
+
+ public static final Migration MIGRATION_9_10 =
+ new Migration(9, 10) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `defaultPostPrivacy` INTEGER"
+ + " NOT NULL DEFAULT 1");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `defaultMediaSensitivity`"
+ + " INTEGER NOT NULL DEFAULT 0");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `alwaysShowSensitiveMedia`"
+ + " INTEGER NOT NULL DEFAULT 0");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `mediaPreviewEnabled` INTEGER"
+ + " NOT NULL DEFAULT '1'");
+ }
+ };
+
+ public static final Migration MIGRATION_10_11 =
+ new Migration(10, 11) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `TimelineAccountEntity` ("
+ + "`serverId` TEXT NOT NULL, "
+ + "`timelineUserId` INTEGER NOT NULL, "
+ + "`instance` TEXT NOT NULL, "
+ + "`localUsername` TEXT NOT NULL, "
+ + "`username` TEXT NOT NULL, "
+ + "`displayName` TEXT NOT NULL, "
+ + "`url` TEXT NOT NULL, "
+ + "`avatar` TEXT NOT NULL, "
+ + "`emojis` TEXT NOT NULL,"
+ + "PRIMARY KEY(`serverId`, `timelineUserId`))");
+
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `TimelineStatusEntity` (`serverId` TEXT NOT"
+ + " NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL,"
+ + " `authorServerId` TEXT,`instance` TEXT, `inReplyToId` TEXT,"
+ + " `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER"
+ + " NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL,"
+ + " `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT"
+ + " NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT"
+ + " NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments`"
+ + " TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId`"
+ + " TEXT, `reblogAccountId` TEXT, PRIMARY KEY(`serverId`,"
+ + " `timelineUserId`), FOREIGN KEY(`authorServerId`,"
+ + " `timelineUserId`) REFERENCES"
+ + " `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE"
+ + " NO ACTION ON DELETE NO ACTION )");
+ database.execSQL(
+ "CREATE INDEX IF NOT"
+ + " EXISTS`index_TimelineStatusEntity_authorServerId_timelineUserId`"
+ + " ON `TimelineStatusEntity` (`authorServerId`,"
+ + " `timelineUserId`)");
+ }
+ };
+
+ public static final Migration MIGRATION_11_12 =
+ new Migration(11, 12) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ String defaultTabs =
+ TabDataKt.HOME
+ + ";"
+ + TabDataKt.NOTIFICATIONS
+ + ";"
+ + TabDataKt.LOCAL
+ + ";"
+ + TabDataKt.FEDERATED;
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `tabPreferences` TEXT NOT NULL"
+ + " DEFAULT '"
+ + defaultTabs
+ + "'");
+
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `ConversationEntity` ("
+ + "`accountId` INTEGER NOT NULL, "
+ + "`id` TEXT NOT NULL, "
+ + "`accounts` TEXT NOT NULL, "
+ + "`unread` INTEGER NOT NULL, "
+ + "`s_id` TEXT NOT NULL, "
+ + "`s_url` TEXT, "
+ + "`s_inReplyToId` TEXT, "
+ + "`s_inReplyToAccountId` TEXT, "
+ + "`s_account` TEXT NOT NULL, "
+ + "`s_content` TEXT NOT NULL, "
+ + "`s_createdAt` INTEGER NOT NULL, "
+ + "`s_emojis` TEXT NOT NULL, "
+ + "`s_favouritesCount` INTEGER NOT NULL, "
+ + "`s_favourited` INTEGER NOT NULL, "
+ + "`s_sensitive` INTEGER NOT NULL, "
+ + "`s_spoilerText` TEXT NOT NULL, "
+ + "`s_attachments` TEXT NOT NULL, "
+ + "`s_mentions` TEXT NOT NULL, "
+ + "`s_showingHiddenContent` INTEGER NOT NULL, "
+ + "`s_expanded` INTEGER NOT NULL, "
+ + "`s_collapsible` INTEGER NOT NULL, "
+ + "`s_collapsed` INTEGER NOT NULL, "
+ + "PRIMARY KEY(`id`, `accountId`))");
+ }
+ };
+
+ public static final Migration MIGRATION_12_13 =
+ new Migration(12, 13) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+
+ database.execSQL("DROP TABLE IF EXISTS `TimelineAccountEntity`");
+ database.execSQL("DROP TABLE IF EXISTS `TimelineStatusEntity`");
+
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `TimelineAccountEntity` ("
+ + "`serverId` TEXT NOT NULL, "
+ + "`timelineUserId` INTEGER NOT NULL, "
+ + "`localUsername` TEXT NOT NULL, "
+ + "`username` TEXT NOT NULL, "
+ + "`displayName` TEXT NOT NULL, "
+ + "`url` TEXT NOT NULL, "
+ + "`avatar` TEXT NOT NULL, "
+ + "`emojis` TEXT NOT NULL,"
+ + "PRIMARY KEY(`serverId`, `timelineUserId`))");
+
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `TimelineStatusEntity` (`serverId` TEXT NOT"
+ + " NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL,"
+ + " `authorServerId` TEXT,`inReplyToId` TEXT, `inReplyToAccountId`"
+ + " TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis`"
+ + " TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount`"
+ + " INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `favourited`"
+ + " INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText`"
+ + " TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions`"
+ + " TEXT, `application` TEXT, `reblogServerId` TEXT,"
+ + " `reblogAccountId` TEXT, PRIMARY KEY(`serverId`,"
+ + " `timelineUserId`), FOREIGN KEY(`authorServerId`,"
+ + " `timelineUserId`) REFERENCES"
+ + " `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE"
+ + " NO ACTION ON DELETE NO ACTION )");
+ database.execSQL(
+ "CREATE INDEX IF NOT"
+ + " EXISTS`index_TimelineStatusEntity_authorServerId_timelineUserId`"
+ + " ON `TimelineStatusEntity` (`authorServerId`,"
+ + " `timelineUserId`)");
+ }
+ };
+
+ public static final Migration MIGRATION_10_13 =
+ new Migration(10, 13) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ MIGRATION_11_12.migrate(database);
+ MIGRATION_12_13.migrate(database);
+ }
+ };
+
+ public static final Migration MIGRATION_13_14 =
+ new Migration(13, 14) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsFilter` TEXT NOT"
+ + " NULL DEFAULT '[]'");
+ }
+ };
+
+ public static final Migration MIGRATION_14_15 =
+ new Migration(14, 15) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `poll` TEXT");
+ database.execSQL("ALTER TABLE `ConversationEntity` ADD COLUMN `s_poll` TEXT");
+ }
+ };
+
+ public static final Migration MIGRATION_15_16 =
+ new Migration(15, 16) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsPolls` INTEGER"
+ + " NOT NULL DEFAULT 1");
+ }
+ };
+
+ public static final Migration MIGRATION_16_17 =
+ new Migration(16, 17) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `TimelineAccountEntity` ADD COLUMN `bot` INTEGER NOT NULL"
+ + " DEFAULT 0");
+ }
+ };
+
+ public static final Migration MIGRATION_17_18 =
+ new Migration(17, 18) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `alwaysOpenSpoiler` INTEGER NOT"
+ + " NULL DEFAULT 0");
+ }
+ };
+
+ public static final Migration MIGRATION_18_19 =
+ new Migration(18, 19) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptions` INTEGER");
+ database.execSQL(
+ "ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptionLength`"
+ + " INTEGER");
+
+ database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `poll` TEXT");
+ }
+ };
+
+ public static final Migration MIGRATION_19_20 =
+ new Migration(19, 20) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `TimelineStatusEntity` ADD COLUMN `bookmarked` INTEGER NOT"
+ + " NULL DEFAULT 0");
+ database.execSQL(
+ "ALTER TABLE `ConversationEntity` ADD COLUMN `s_bookmarked` INTEGER NOT"
+ + " NULL DEFAULT 0");
+ }
+ };
+
+ public static final Migration MIGRATION_20_21 =
+ new Migration(20, 21) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `version` TEXT");
+ database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `markdownMode` INTEGER");
+ }
+ };
+
+ public static final Migration MIGRATION_21_22 =
+ new Migration(21, 22) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsEmojiReactions`"
+ + " INTEGER NOT NULL DEFAULT 1");
+ }
+ };
+
+ public static final Migration MIGRATION_22_23 =
+ new Migration(22, 23) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ // leave markdownMode unused, we don't need it anymore but don't recreate table
+ // database.execSQL("ALTER TABLE `TootEntity` DROP COLUMN `markdownMode`");
+ database.execSQL(
+ "ALTER TABLE `TootEntity` ADD COLUMN `formattingSyntax` TEXT NOT NULL"
+ + " DEFAULT ''");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `defaultFormattingSyntax` TEXT"
+ + " NOT NULL DEFAULT ''");
+ }
+ };
+
+ public static final Migration MIGRATION_23_24 =
+ new Migration(23, 24) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsFollowRequested`"
+ + " INTEGER NOT NULL DEFAULT 1");
+ }
+ };
+
+ public static final Migration MIGRATION_24_25 =
+ new Migration(24, 25) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE `ChatEntity` (`localId` INTEGER NOT NULL,"
+ + "`chatId` TEXT NOT NULL,"
+ + "`accountId` TEXT NOT NULL,"
+ + "`unread` INTEGER NOT NULL,"
+ + "`updatedAt` INTEGER NOT NULL,"
+ + "`lastMessageId` TEXT,"
+ + "PRIMARY KEY (`localId`, `chatId`))");
+ database.execSQL(
+ "CREATE TABLE `ChatMessageEntity` (`localId` INTEGER NOT NULL,"
+ + "`messageId` TEXT NOT NULL,"
+ + "`content` TEXT,"
+ + "`chatId` TEXT NOT NULL,"
+ + "`accountId` TEXT NOT NULL,"
+ + "`createdAt` INTEGER NOT NULL,"
+ + "`attachment` TEXT,"
+ + "`emojis` TEXT NOT NULL,"
+ + "PRIMARY KEY (`localId`, `messageId`))");
+ database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `chatLimit` INTEGER");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsChatMessages`"
+ + " INTEGER NOT NULL DEFAULT 1");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsStreamingEnabled`"
+ + " INTEGER NOT NULL DEFAULT 1");
+ database.execSQL(
+ "ALTER TABLE `TimelineStatusEntity` ADD COLUMN `pleroma` TEXT");
+ }
+ };
+
+ public static final Migration MIGRATION_25_26 =
+ new Migration(25, 26) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsSubscriptions`"
+ + " INTEGER NOT NULL DEFAULT 1");
+ database.execSQL(
+ "ALTER TABLE `AccountEntity` ADD COLUMN `notificationsMove` INTEGER NOT"
+ + " NULL DEFAULT 1");
+ }
+ };
+
+ public static final Migration MIGRATION_26_27 =
+ new Migration(26, 27) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "CREATE TABLE IF NOT EXISTS `DraftEntity` ("
+ + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ + "`accountId` INTEGER NOT NULL, "
+ + "`inReplyToId` TEXT,"
+ + "`content` TEXT,"
+ + "`contentWarning` TEXT,"
+ + "`sensitive` INTEGER NOT NULL,"
+ + "`visibility` INTEGER NOT NULL,"
+ + "`attachments` TEXT NOT NULL,"
+ + "`poll` TEXT,"
+ + "`formattingSyntax` TEXT NOT NULL,"
+ + "`failedToSend` INTEGER NOT NULL)");
+ }
+ };
+
+ public static final Migration MIGRATION_27_28 =
+ new Migration(27, 28) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL(
+ "ALTER TABLE `TimelineStatusEntity` ADD COLUMN `editedAt` INTEGER");
+ database.execSQL(
+ "ALTER TABLE `ConversationEntity` ADD COLUMN `s_editedAt` INTEGER");
+ }
+ };
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java b/husky/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java
index f46c275..652a2a5 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java
@@ -17,17 +17,14 @@ package com.keylesspalace.tusky.db;
import androidx.room.Dao;
import androidx.room.Query;
-
-import java.util.List;
-
import io.reactivex.Observable;
+import java.util.List;
/**
* Created by cto3543 on 28/06/2017.
*
- * DAO to fetch and update toots in the DB.
+ * <p>DAO to fetch and update toots in the DB.
*/
-
@Dao
public interface TootDao {
@@ -42,4 +39,4 @@ public interface TootDao {
@Query("SELECT COUNT(*) FROM TootEntity")
Observable<Integer> savedTootCount();
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java b/husky/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java
index d40bcc7..aea0096 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java
@@ -15,21 +15,17 @@
package com.keylesspalace.tusky.db;
-import com.google.gson.Gson;
-import com.keylesspalace.tusky.entity.NewPoll;
-import com.keylesspalace.tusky.entity.Status;
-
import androidx.annotation.*;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverter;
import androidx.room.TypeConverters;
+import com.google.gson.Gson;
+import com.keylesspalace.tusky.entity.NewPoll;
+import com.keylesspalace.tusky.entity.Status;
-/**
- * Toot model.
- */
-
+/** Toot model. */
@Entity
@TypeConverters(TootEntity.Converters.class)
public class TootEntity {
@@ -65,19 +61,28 @@ public class TootEntity {
@Nullable
@ColumnInfo(name = "poll")
private final NewPoll poll;
-
+
@NonNull
@ColumnInfo(name = "formattingSyntax")
private final String formattingSyntax;
-
+
/* DEPRECATED */
@Nullable
@ColumnInfo(name = "markdownMode")
public Boolean markdownMode = false;
- public TootEntity(int uid, String text, String urls, String descriptions, String contentWarning, String inReplyToId,
- @Nullable String inReplyToText, @Nullable String inReplyToUsername,
- Status.Visibility visibility, @Nullable NewPoll poll, String formattingSyntax) {
+ public TootEntity(
+ int uid,
+ String text,
+ String urls,
+ String descriptions,
+ String contentWarning,
+ String inReplyToId,
+ @Nullable String inReplyToText,
+ @Nullable String inReplyToUsername,
+ Status.Visibility visibility,
+ @Nullable NewPoll poll,
+ String formattingSyntax) {
this.uid = uid;
this.text = text;
this.urls = urls;
@@ -133,11 +138,11 @@ public class TootEntity {
public NewPoll getPoll() {
return poll;
}
-
+
public String getFormattingSyntax() {
return formattingSyntax;
}
-
+
@Nullable
public Boolean getMarkdownMode() {
return markdownMode;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
index b674b8b..7134c2e 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
@@ -18,10 +18,8 @@ package com.keylesspalace.tusky.fragment;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
-
import java.util.ArrayList;
import java.util.List;
-
import retrofit2.Call;
public class BaseFragment extends Fragment {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
index 59da514..3b3beee 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
@@ -20,6 +20,11 @@
package com.keylesspalace.tusky.fragment;
+import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
+import static com.uber.autodispose.AutoDispose.autoDisposable;
+import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
@@ -86,14 +91,11 @@ import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.NotificationTypeConverterKt;
import com.keylesspalace.tusky.util.PairedList;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
-import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
import com.keylesspalace.tusky.util.ViewDataUtils;
import com.keylesspalace.tusky.view.BackgroundMessageView;
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import static com.uber.autodispose.AutoDispose.autoDisposable;
-import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -110,31 +112,33 @@ import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import okhttp3.ResponseBody;
-import static org.koin.java.KoinJavaComponent.inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class NotificationsFragment extends SFragment
- implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener,
- NotificationsAdapter.NotificationActionListener, AccountActionListener, ReselectableFragment
-{
+ implements SwipeRefreshLayout.OnRefreshListener,
+ StatusActionListener,
+ NotificationsAdapter.NotificationActionListener,
+ AccountActionListener,
+ ReselectableFragment {
private static final String TAG = "NotificationF"; // logging tag
private static final int LOAD_AT_ONCE = 30;
private int maxPlaceholderId = 0;
-
private Set<Notification.Type> notificationFilter = new HashSet<>();
private enum FetchEnd {
- TOP, BOTTOM, MIDDLE
+ TOP,
+ BOTTOM,
+ MIDDLE
}
/**
- * Placeholder for the notificationsEnabled. Consider moving to the separate class to hide constructor
- * and reuse in different places as needed.
+ * Placeholder for the notificationsEnabled. Consider moving to the separate class to hide
+ * constructor and reuse in different places as needed.
*/
private static final class Placeholder {
final long id;
@@ -171,22 +175,32 @@ public class NotificationsFragment extends SFragment
// Each element is either a Notification for loading data or a Placeholder
private final PairedList<Either<Placeholder, Notification>, NotificationViewData>
- notifications =
- new PairedList<>(new Function<Either<Placeholder, Notification>, NotificationViewData>() {
- @Override
- public NotificationViewData apply(Either<Placeholder, Notification> input) {
- if(input.isRight()) {
- Notification notification =
- Notification.rewriteToStatusTypeIfNeeded(input.asRight(),
- accountManager.getValue().getActiveAccount().getAccountId());
-
- return ViewDataUtils.notificationToViewData(notification,
- alwaysShowSensitiveMedia, alwaysOpenSpoiler);
- } else {
- return new NotificationViewData.Placeholder(input.asLeft().id, false);
- }
- }
- });
+ notifications =
+ new PairedList<>(
+ new Function<
+ Either<Placeholder, Notification>, NotificationViewData>() {
+ @Override
+ public NotificationViewData apply(
+ Either<Placeholder, Notification> input) {
+ if (input.isRight()) {
+ Notification notification =
+ Notification.rewriteToStatusTypeIfNeeded(
+ input.asRight(),
+ accountManager
+ .getValue()
+ .getActiveAccount()
+ .getAccountId());
+
+ return ViewDataUtils.notificationToViewData(
+ notification,
+ alwaysShowSensitiveMedia,
+ alwaysOpenSpoiler);
+ } else {
+ return new NotificationViewData.Placeholder(
+ input.asLeft().id, false);
+ }
+ }
+ });
public static NotificationsFragment newInstance() {
NotificationsFragment fragment = new NotificationsFragment();
@@ -197,19 +211,20 @@ public class NotificationsFragment extends SFragment
@Nullable
@Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState)
- {
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
View rootView =
- inflater.inflate(R.layout.fragment_timeline_notifications, container, false);
+ inflater.inflate(R.layout.fragment_timeline_notifications, container, false);
@NonNull Context context = inflater.getContext(); // from inflater to silence warning
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean showNotificationsFilterSetting =
- preferences.getBoolean("showNotificationsFilter", true);
- //Clear notifications on filter visibility change to force refresh
- if(showNotificationsFilterSetting != showNotificationsFilter) {
+ preferences.getBoolean("showNotificationsFilter", true);
+ // Clear notifications on filter visibility change to force refresh
+ if (showNotificationsFilterSetting != showNotificationsFilter) {
notifications.clear();
}
showNotificationsFilter = showNotificationsFilterSetting;
@@ -231,35 +246,47 @@ public class NotificationsFragment extends SFragment
layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAccessibilityDelegateCompat(
- new ListStatusAccessibilityDelegate(recyclerView, this, (pos) -> {
- NotificationViewData notification = notifications.getPairedItemOrNull(pos);
- // We support replies only for now
- if(notification instanceof NotificationViewData.Concrete) {
- return ((NotificationViewData.Concrete) notification).getStatusViewData();
- } else {
- return null;
- }
- }));
+ new ListStatusAccessibilityDelegate(
+ recyclerView,
+ this,
+ (pos) -> {
+ NotificationViewData notification =
+ notifications.getPairedItemOrNull(pos);
+ // We support replies only for now
+ if (notification instanceof NotificationViewData.Concrete) {
+ return ((NotificationViewData.Concrete) notification)
+ .getStatusViewData();
+ } else {
+ return null;
+ }
+ }));
recyclerView.addItemDecoration(
- new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
+ new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
StatusDisplayOptions statusDisplayOptions =
- new StatusDisplayOptions(preferences.getBoolean("animateGifAvatars", false),
- accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
- preferences.getBoolean("absoluteTimeView", false),
- preferences.getBoolean("showBotOverlay", true),
- preferences.getBoolean("useBlurhash", true), CardViewMode.NONE,
- preferences.getBoolean("confirmReblogs", true),
- preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
- preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
+ new StatusDisplayOptions(
+ preferences.getBoolean("animateGifAvatars", false),
+ accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
+ preferences.getBoolean("absoluteTimeView", false),
+ preferences.getBoolean("showBotOverlay", true),
+ preferences.getBoolean("useBlurhash", true),
+ CardViewMode.NONE,
+ preferences.getBoolean("confirmReblogs", true),
+ preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
+ preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
withMuted = !preferences.getBoolean(PrefKeys.HIDE_MUTED_USERS, false);
adapter =
- new NotificationsAdapter(accountManager.getValue().getActiveAccount().getAccountId(),
- dataSource, statusDisplayOptions, this, this, this);
+ new NotificationsAdapter(
+ accountManager.getValue().getActiveAccount().getAccountId(),
+ dataSource,
+ statusDisplayOptions,
+ this,
+ this,
+ this);
alwaysShowSensitiveMedia =
- accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
+ accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getValue().getActiveAccount().getAlwaysOpenSpoiler();
recyclerView.setAdapter(adapter);
@@ -274,7 +301,7 @@ public class NotificationsFragment extends SFragment
buttonFilter = rootView.findViewById(R.id.buttonFilter);
buttonFilter.setOnClickListener(v -> showFilterMenu());
- if(notifications.isEmpty()) {
+ if (notifications.isEmpty()) {
swipeRefreshLayout.setEnabled(false);
sendFetchNotificationsRequest(null, null, FetchEnd.BOTTOM, -1);
} else {
@@ -290,82 +317,93 @@ public class NotificationsFragment extends SFragment
private void updateFilterVisibility() {
CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
- if(showNotificationsFilter && !showingError) {
+ (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+ if (showNotificationsFilter && !showingError) {
appBarOptions.setExpanded(true, false);
appBarOptions.setVisibility(View.VISIBLE);
- //Set content behaviour to hide filter on scroll
+ // Set content behaviour to hide filter on scroll
params.setBehavior(new AppBarLayout.ScrollingViewBehavior());
} else {
appBarOptions.setExpanded(false, false);
appBarOptions.setVisibility(View.GONE);
- //Clear behaviour to hide app bar
+ // Clear behaviour to hide app bar
params.setBehavior(null);
}
}
private void confirmClearNotifications() {
- new AlertDialog.Builder(getContext()).setMessage(R.string.notification_clear_text)
- .setPositiveButton(android.R.string.yes,
- (DialogInterface dia, int which) -> clearNotifications())
- .setNegativeButton(android.R.string.no, null).show();
+ new AlertDialog.Builder(getContext())
+ .setMessage(R.string.notification_clear_text)
+ .setPositiveButton(
+ android.R.string.yes,
+ (DialogInterface dia, int which) -> clearNotifications())
+ .setNegativeButton(android.R.string.no, null)
+ .show();
}
private void handleFavEvent(FavoriteEvent event) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(event.getStatusId());
- if(posAndNotification == null) {
+ if (posAndNotification == null) {
return;
}
//noinspection ConstantConditions
- setFavouriteForStatus(posAndNotification.first, posAndNotification.second.getStatus(),
- event.getFavourite());
+ setFavouriteForStatus(
+ posAndNotification.first,
+ posAndNotification.second.getStatus(),
+ event.getFavourite());
}
private void handleBookmarkEvent(BookmarkEvent event) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(event.getStatusId());
- if(posAndNotification == null) {
+ if (posAndNotification == null) {
return;
}
//noinspection ConstantConditions
- setBookmarkForStatus(posAndNotification.first, posAndNotification.second.getStatus(),
- event.getBookmark());
+ setBookmarkForStatus(
+ posAndNotification.first,
+ posAndNotification.second.getStatus(),
+ event.getBookmark());
}
private void handleReblogEvent(ReblogEvent event) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(event.getStatusId());
- if(posAndNotification == null) {
+ if (posAndNotification == null) {
return;
}
//noinspection ConstantConditions
- setReblogForStatus(posAndNotification.first, posAndNotification.second.getStatus(),
- event.getReblog());
+ setReblogForStatus(
+ posAndNotification.first, posAndNotification.second.getStatus(), event.getReblog());
}
private void handleMuteStatusEvent(MuteConversationEvent event) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(event.getStatusId());
- if(posAndNotification == null) {
+ if (posAndNotification == null) {
return;
}
String conversationId = posAndNotification.second.getStatus().getConversationId();
- if(conversationId.isEmpty()) { // invalid conversation ID
- if(withMuted) {
- setMutedStatusForStatus(posAndNotification.first,
- posAndNotification.second.getStatus(), event.getMute(), event.getMute());
+ if (conversationId.isEmpty()) { // invalid conversation ID
+ if (withMuted) {
+ setMutedStatusForStatus(
+ posAndNotification.first,
+ posAndNotification.second.getStatus(),
+ event.getMute(),
+ event.getMute());
} else {
notifications.remove(posAndNotification.first);
}
} else {
//noinspection ConstantConditions
- if(withMuted) {
- for(int i = 0; i < notifications.size(); i++) {
+ if (withMuted) {
+ for (int i = 0; i < notifications.size(); i++) {
Notification notification = notifications.get(i).asRightOrNull();
- if(notification != null && notification.getStatus() != null &&
- notification.getType() == Notification.Type.MENTION &&
- notification.getStatus().getConversationId() == conversationId) {
- setMutedStatusForStatus(i, notification.getStatus(), event.getMute(),
- event.getMute());
+ if (notification != null
+ && notification.getStatus() != null
+ && notification.getType() == Notification.Type.MENTION
+ && notification.getStatus().getConversationId() == conversationId) {
+ setMutedStatusForStatus(
+ i, notification.getStatus(), event.getMute(), event.getMute());
}
}
} else {
@@ -379,13 +417,14 @@ public class NotificationsFragment extends SFragment
String id = event.getAccountId();
boolean mute = event.getMute();
- if(withMuted) {
- for(int i = 0; i < notifications.size(); i++) {
+ if (withMuted) {
+ for (int i = 0; i < notifications.size(); i++) {
Notification notification = notifications.get(i).asRightOrNull();
- if(notification != null && notification.getStatus() != null &&
- notification.getType() == Notification.Type.MENTION &&
- notification.getAccount().getId().equals(id) &&
- !notification.getStatus().isThreadMuted()) {
+ if (notification != null
+ && notification.getStatus() != null
+ && notification.getType() == Notification.Type.MENTION
+ && notification.getAccount().getId().equals(id)
+ && !notification.getStatus().isThreadMuted()) {
setMutedStatusForStatus(i, notification.getStatus(), mute, false);
}
}
@@ -399,7 +438,7 @@ public class NotificationsFragment extends SFragment
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
- if(activity == null) {
+ if (activity == null) {
throw new AssertionError("Activity is null");
}
@@ -409,55 +448,61 @@ public class NotificationsFragment extends SFragment
* the compose button on down-scroll. */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
hideFab = preferences.getBoolean("fabHide", false);
- scrollListener = new EndlessOnScrollListener(layoutManager) {
- @Override
- public void onScrolled(@NonNull RecyclerView view, int dx, int dy) {
- super.onScrolled(view, dx, dy);
-
- ActionButtonActivity activity = (ActionButtonActivity) getActivity();
- FloatingActionButton composeButton = activity.getActionButton();
-
- if(composeButton != null) {
- if(hideFab) {
- if(dy > 0 && composeButton.isShown()) {
- composeButton.hide(); // hides the button if we're scrolling down
- } else if(dy < 0 && !composeButton.isShown()) {
- composeButton.show(); // shows it if we are scrolling up
+ scrollListener =
+ new EndlessOnScrollListener(layoutManager) {
+ @Override
+ public void onScrolled(@NonNull RecyclerView view, int dx, int dy) {
+ super.onScrolled(view, dx, dy);
+
+ ActionButtonActivity activity = (ActionButtonActivity) getActivity();
+ FloatingActionButton composeButton = activity.getActionButton();
+
+ if (composeButton != null) {
+ if (hideFab) {
+ if (dy > 0 && composeButton.isShown()) {
+ composeButton
+ .hide(); // hides the button if we're scrolling down
+ } else if (dy < 0 && !composeButton.isShown()) {
+ composeButton.show(); // shows it if we are scrolling up
+ }
+ } else if (!composeButton.isShown()) {
+ composeButton.show();
+ }
}
- } else if(!composeButton.isShown()) {
- composeButton.show();
}
- }
- }
- @Override
- public void onLoadMore(int totalItemsCount, RecyclerView view) {
- NotificationsFragment.this.onLoadMore();
- }
- };
+ @Override
+ public void onLoadMore(int totalItemsCount, RecyclerView view) {
+ NotificationsFragment.this.onLoadMore();
+ }
+ };
recyclerView.addOnScrollListener(scrollListener);
- eventHub.getEvents().observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))).subscribe(event -> {
- if(event instanceof FavoriteEvent) {
- handleFavEvent((FavoriteEvent) event);
- } else if(event instanceof BookmarkEvent) {
- handleBookmarkEvent((BookmarkEvent) event);
- } else if(event instanceof ReblogEvent) {
- handleReblogEvent((ReblogEvent) event);
- } else if(event instanceof MuteConversationEvent) {
- handleMuteStatusEvent((MuteConversationEvent) event);
- } else if(event instanceof BlockEvent) {
- removeAllByAccountId(((BlockEvent) event).getAccountId());
- } else if(event instanceof MuteEvent) {
- handleMuteEvent((MuteEvent) event);
- } else if(event instanceof PreferenceChangedEvent) {
- onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
- } else if(event instanceof EmojiReactEvent) {
- handleEmojiReactEvent((EmojiReactEvent) event);
- }
- });
+ eventHub.getEvents()
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ event -> {
+ if (event instanceof FavoriteEvent) {
+ handleFavEvent((FavoriteEvent) event);
+ } else if (event instanceof BookmarkEvent) {
+ handleBookmarkEvent((BookmarkEvent) event);
+ } else if (event instanceof ReblogEvent) {
+ handleReblogEvent((ReblogEvent) event);
+ } else if (event instanceof MuteConversationEvent) {
+ handleMuteStatusEvent((MuteConversationEvent) event);
+ } else if (event instanceof BlockEvent) {
+ removeAllByAccountId(((BlockEvent) event).getAccountId());
+ } else if (event instanceof MuteEvent) {
+ handleMuteEvent((MuteEvent) event);
+ } else if (event instanceof PreferenceChangedEvent) {
+ onPreferenceChanged(
+ ((PreferenceChangedEvent) event).getPreferenceKey());
+ } else if (event instanceof EmojiReactEvent) {
+ handleEmojiReactEvent((EmojiReactEvent) event);
+ }
+ });
}
@Override
@@ -466,7 +511,7 @@ public class NotificationsFragment extends SFragment
this.showingError = false;
Either<Placeholder, Notification> first = CollectionsKt.firstOrNull(this.notifications);
String topId;
- if(first != null && first.isRight()) {
+ if (first != null && first.isRight()) {
topId = first.asRight().getId();
} else {
topId = null;
@@ -484,31 +529,43 @@ public class NotificationsFragment extends SFragment
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
Objects.requireNonNull(status, "Reblog on notification without status");
- timelineCases.getValue().reblog(status, reblog).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setReblogForStatus(position, status, reblog),
- (t) -> Log.d(getClass().getSimpleName(),
- "Failed to reblog status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .reblog(status, reblog)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setReblogForStatus(position, status, reblog),
+ (t) ->
+ Log.d(
+ getClass().getSimpleName(),
+ "Failed to reblog status: " + status.getId(),
+ t));
}
private void setReblogForStatus(int position, Status status, boolean reblog) {
status.setReblogged(reblog);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setReblogged(reblog);
}
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder =
- new StatusViewData.Builder(viewdata.getStatusViewData());
+ new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setReblogged(reblog);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
- viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ viewDataBuilder.createStatusViewData(),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
updateAdapter();
}
@@ -518,31 +575,43 @@ public class NotificationsFragment extends SFragment
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
- timelineCases.getValue().favourite(status, favourite)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setFavouriteForStatus(position, status, favourite),
- (t) -> Log.d(getClass().getSimpleName(),
- "Failed to favourite status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .favourite(status, favourite)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setFavouriteForStatus(position, status, favourite),
+ (t) ->
+ Log.d(
+ getClass().getSimpleName(),
+ "Failed to favourite status: " + status.getId(),
+ t));
}
private void setFavouriteForStatus(int position, Status status, boolean favourite) {
status.setFavourited(favourite);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setFavourited(favourite);
}
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder =
- new StatusViewData.Builder(viewdata.getStatusViewData());
+ new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setFavourited(favourite);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
- viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ viewDataBuilder.createStatusViewData(),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
updateAdapter();
@@ -553,31 +622,43 @@ public class NotificationsFragment extends SFragment
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
- timelineCases.getValue().bookmark(status, bookmark)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setBookmarkForStatus(position, status, bookmark),
- (t) -> Log.d(getClass().getSimpleName(),
- "Failed to bookmark status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .bookmark(status, bookmark)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setBookmarkForStatus(position, status, bookmark),
+ (t) ->
+ Log.d(
+ getClass().getSimpleName(),
+ "Failed to bookmark status: " + status.getId(),
+ t));
}
private void setBookmarkForStatus(int position, Status status, boolean bookmark) {
status.setBookmarked(bookmark);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setBookmarked(bookmark);
}
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder =
- new StatusViewData.Builder(viewdata.getStatusViewData());
+ new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setBookmarked(bookmark);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
- viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ viewDataBuilder.createStatusViewData(),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
updateAdapter();
@@ -587,25 +668,34 @@ public class NotificationsFragment extends SFragment
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
- timelineCases.getValue().voteInPoll(status, choices)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newPoll) -> setVoteForPoll(position, newPoll),
- (t) -> Log.d(TAG, "Failed to vote in poll: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .voteInPoll(status, choices)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newPoll) -> setVoteForPoll(position, newPoll),
+ (t) -> Log.d(TAG, "Failed to vote in poll: " + status.getId(), t));
}
private void setVoteForPoll(int position, Poll poll) {
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder =
- new StatusViewData.Builder(viewdata.getStatusViewData());
+ new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setPoll(poll);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
- viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ viewDataBuilder.createStatusViewData(),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
updateAdapter();
@@ -620,7 +710,7 @@ public class NotificationsFragment extends SFragment
@Override
public void onViewMedia(int position, int attachmentIndex, @Nullable View view) {
Notification notification = notifications.get(position).asRightOrNull();
- if(notification == null || notification.getStatus() == null) {
+ if (notification == null || notification.getStatus() == null) {
return;
}
super.viewMedia(attachmentIndex, notification.getStatus(), view);
@@ -635,7 +725,7 @@ public class NotificationsFragment extends SFragment
@Override
public void onViewReplyTo(int position) {
Notification notification = notifications.get(position).asRightOrNull();
- if(notification == null) {
+ if (notification == null) {
return;
}
super.onShowReplyTo(notification.getStatus().getInReplyToId());
@@ -650,13 +740,20 @@ public class NotificationsFragment extends SFragment
@Override
public void onExpandedChange(boolean expanded, int position) {
NotificationViewData.Concrete old =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Concrete statusViewData =
- new StatusViewData.Builder(old.getStatusViewData()).setIsExpanded(expanded)
- .createStatusViewData();
+ new StatusViewData.Builder(old.getStatusViewData())
+ .setIsExpanded(expanded)
+ .createStatusViewData();
NotificationViewData notificationViewData =
- new NotificationViewData.Concrete(old.getType(), old.getId(), old.getAccount(),
- statusViewData, old.getEmoji(), old.getEmojiUrl(), old.getTarget());
+ new NotificationViewData.Concrete(
+ old.getType(),
+ old.getId(),
+ old.getAccount(),
+ statusViewData,
+ old.getEmoji(),
+ old.getEmojiUrl(),
+ old.getTarget());
notifications.setPairedItem(position, notificationViewData);
updateAdapter();
}
@@ -664,13 +761,20 @@ public class NotificationsFragment extends SFragment
@Override
public void onContentHiddenChange(boolean isShowing, int position) {
NotificationViewData.Concrete old =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Concrete statusViewData =
- new StatusViewData.Builder(old.getStatusViewData()).setIsShowingSensitiveContent(
- isShowing).createStatusViewData();
+ new StatusViewData.Builder(old.getStatusViewData())
+ .setIsShowingSensitiveContent(isShowing)
+ .createStatusViewData();
NotificationViewData notificationViewData =
- new NotificationViewData.Concrete(old.getType(), old.getId(), old.getAccount(),
- statusViewData, old.getEmoji(), old.getEmojiUrl(), old.getTarget());
+ new NotificationViewData.Concrete(
+ old.getType(),
+ old.getId(),
+ old.getAccount(),
+ statusViewData,
+ old.getEmoji(),
+ old.getEmojiUrl(),
+ old.getTarget());
notifications.setPairedItem(position, notificationViewData);
updateAdapter();
}
@@ -678,54 +782,64 @@ public class NotificationsFragment extends SFragment
@Override
public void onMute(int position, boolean isMuted) {
NotificationViewData.Concrete old =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Concrete statusViewData =
- new StatusViewData.Builder(old.getStatusViewData()).setMuted(isMuted)
- .createStatusViewData();
+ new StatusViewData.Builder(old.getStatusViewData())
+ .setMuted(isMuted)
+ .createStatusViewData();
NotificationViewData notificationViewData =
- new NotificationViewData.Concrete(old.getType(), old.getId(), old.getAccount(),
- statusViewData, old.getEmoji(), old.getEmojiUrl(), old.getTarget());
+ new NotificationViewData.Concrete(
+ old.getType(),
+ old.getId(),
+ old.getAccount(),
+ statusViewData,
+ old.getEmoji(),
+ old.getEmojiUrl(),
+ old.getTarget());
notifications.setPairedItem(position, notificationViewData);
updateAdapter();
}
- private void setMutedStatusForStatus(int position, Status status, boolean muted,
- boolean threadMuted)
- {
+ private void setMutedStatusForStatus(
+ int position, Status status, boolean muted, boolean threadMuted) {
status.setThreadMuted(threadMuted);
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder =
- new StatusViewData.Builder(viewdata.getStatusViewData());
+ new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setThreadMuted(threadMuted);
viewDataBuilder.setMuted(muted);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
- viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ viewDataBuilder.createStatusViewData(),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
}
-
@Override
public void onLoadMore(int position) {
- //check bounds before accessing list,
- if(notifications.size() >= position && position > 0) {
+ // check bounds before accessing list,
+ if (notifications.size() >= position && position > 0) {
Notification previous = notifications.get(position - 1).asRightOrNull();
Notification next = notifications.get(position + 1).asRightOrNull();
- if(previous == null || next == null) {
+ if (previous == null || next == null) {
Log.e(TAG, "Failed to load more, invalid placeholder position: " + position);
return;
}
- sendFetchNotificationsRequest(previous.getId(), next.getId(), FetchEnd.MIDDLE,
- position);
+ sendFetchNotificationsRequest(
+ previous.getId(), next.getId(), FetchEnd.MIDDLE, position);
Placeholder placeholder = notifications.get(position).asLeft();
NotificationViewData notificationViewData =
- new NotificationViewData.Placeholder(placeholder.id, true);
+ new NotificationViewData.Placeholder(placeholder.id, true);
notifications.setPairedItem(position, notificationViewData);
updateAdapter();
} else {
@@ -735,34 +849,44 @@ public class NotificationsFragment extends SFragment
@Override
public void onContentCollapsedChange(boolean isCollapsed, int position) {
- if(position < 0 || position >= notifications.size()) {
- Log.e(TAG,
- String.format("Tried to access out of bounds status position: %d of %d", position,
- notifications.size() - 1));
+ if (position < 0 || position >= notifications.size()) {
+ Log.e(
+ TAG,
+ String.format(
+ "Tried to access out of bounds status position: %d of %d",
+ position, notifications.size() - 1));
return;
}
NotificationViewData notification = notifications.getPairedItem(position);
- if(!(notification instanceof NotificationViewData.Concrete)) {
- Log.e(TAG, String.format(
- "Expected NotificationViewData.Concrete, got %s instead at position: %d of %d",
- notification == null ? "null" : notification.getClass().getSimpleName(), position,
- notifications.size() - 1));
+ if (!(notification instanceof NotificationViewData.Concrete)) {
+ Log.e(
+ TAG,
+ String.format(
+ "Expected NotificationViewData.Concrete, got %s instead at position: %d"
+ + " of %d",
+ notification == null ? "null" : notification.getClass().getSimpleName(),
+ position,
+ notifications.size() - 1));
return;
}
StatusViewData.Concrete status =
- ((NotificationViewData.Concrete) notification).getStatusViewData();
+ ((NotificationViewData.Concrete) notification).getStatusViewData();
StatusViewData.Concrete updatedStatus =
- new StatusViewData.Builder(status).setCollapsed(isCollapsed).createStatusViewData();
+ new StatusViewData.Builder(status).setCollapsed(isCollapsed).createStatusViewData();
NotificationViewData.Concrete concreteNotification =
- (NotificationViewData.Concrete) notification;
+ (NotificationViewData.Concrete) notification;
NotificationViewData updatedNotification =
- new NotificationViewData.Concrete(concreteNotification.getType(),
- concreteNotification.getId(), concreteNotification.getAccount(), updatedStatus,
- concreteNotification.getEmoji(), concreteNotification.getEmojiUrl(),
- concreteNotification.getTarget());
+ new NotificationViewData.Concrete(
+ concreteNotification.getType(),
+ concreteNotification.getId(),
+ concreteNotification.getAccount(),
+ updatedStatus,
+ concreteNotification.getEmoji(),
+ concreteNotification.getEmojiUrl(),
+ concreteNotification.getTarget());
notifications.setPairedItem(position, updatedNotification);
updateAdapter();
@@ -779,64 +903,64 @@ public class NotificationsFragment extends SFragment
}
private void clearNotifications() {
- //Cancel all ongoing requests
+ // Cancel all ongoing requests
swipeRefreshLayout.setRefreshing(false);
resetNotificationsLoad();
- //Show friend elephant
+ // Show friend elephant
this.statusView.setVisibility(View.VISIBLE);
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
updateFilterVisibility();
- //Update adapter
+ // Update adapter
updateAdapter();
- //Execute clear notifications request
+ // Execute clear notifications request
Call<ResponseBody> call = mastodonApi.getValue().clearNotifications();
- call.enqueue(new Callback<ResponseBody>() {
- @Override
- public void onResponse(@NonNull Call<ResponseBody> call,
- @NonNull Response<ResponseBody> response)
- {
- if(isAdded()) {
- if(!response.isSuccessful()) {
- //Reload notifications on failure
- fullyRefreshWithProgressBar(true);
+ call.enqueue(
+ new Callback<ResponseBody>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<ResponseBody> call,
+ @NonNull Response<ResponseBody> response) {
+ if (isAdded()) {
+ if (!response.isSuccessful()) {
+ // Reload notifications on failure
+ fullyRefreshWithProgressBar(true);
+ }
+ }
}
- }
- }
- @Override
- public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
- //Reload notifications on failure
- fullyRefreshWithProgressBar(true);
- }
- });
+ @Override
+ public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
+ // Reload notifications on failure
+ fullyRefreshWithProgressBar(true);
+ }
+ });
callList.add(call);
}
private void resetNotificationsLoad() {
- for(Call callItem : callList) {
+ for (Call callItem : callList) {
callItem.cancel();
}
callList.clear();
bottomLoading = false;
topLoading = false;
- //Disable load more
+ // Disable load more
bottomId = null;
- //Clear exists notifications
+ // Clear exists notifications
notifications.clear();
}
-
private void showFilterMenu() {
List<Notification.Type> notificationsList = Notification.Type.Companion.getAsList();
List<String> list = new ArrayList<>();
- for(Notification.Type type : notificationsList) {
+ for (Notification.Type type : notificationsList) {
// ignore chat messages, as we don't work with them in main notification fragment
- if(type == Notification.Type.CHAT_MESSAGE) {
+ if (type == Notification.Type.CHAT_MESSAGE) {
continue;
}
@@ -844,29 +968,31 @@ public class NotificationsFragment extends SFragment
}
ArrayAdapter<String> adapter =
- new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_multiple_choice,
- list);
+ new ArrayAdapter<>(
+ getContext(), android.R.layout.simple_list_item_multiple_choice, list);
PopupWindow window = new PopupWindow(getContext());
- View view = LayoutInflater.from(getContext())
- .inflate(R.layout.notifications_filter, (ViewGroup) getView(), false);
+ View view =
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notifications_filter, (ViewGroup) getView(), false);
final ListView listView = view.findViewById(R.id.listView);
- view.findViewById(R.id.buttonApply).setOnClickListener(v -> {
- SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
- Set<Notification.Type> excludes = new HashSet<>();
- for(int i = 0; i < notificationsList.size(); i++) {
- if(!checkedItems.get(i, false)) {
- excludes.add(notificationsList.get(i));
- }
- }
- window.dismiss();
- applyFilterChanges(excludes);
-
- });
+ view.findViewById(R.id.buttonApply)
+ .setOnClickListener(
+ v -> {
+ SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
+ Set<Notification.Type> excludes = new HashSet<>();
+ for (int i = 0; i < notificationsList.size(); i++) {
+ if (!checkedItems.get(i, false)) {
+ excludes.add(notificationsList.get(i));
+ }
+ }
+ window.dismiss();
+ applyFilterChanges(excludes);
+ });
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- for(int i = 0; i < notificationsList.size(); i++) {
- if(!notificationFilter.contains(notificationsList.get(i))) {
+ for (int i = 0; i < notificationsList.size(); i++) {
+ if (!notificationFilter.contains(notificationsList.get(i))) {
listView.setItemChecked(i, true);
}
}
@@ -875,11 +1001,10 @@ public class NotificationsFragment extends SFragment
window.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
window.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
window.showAsDropDown(buttonFilter);
-
}
private String getNotificationText(Notification.Type type) {
- switch(type) {
+ switch (type) {
case MENTION:
return getString(R.string.notification_mention_name);
case FAVOURITE:
@@ -906,36 +1031,35 @@ public class NotificationsFragment extends SFragment
private void applyFilterChanges(Set<Notification.Type> newSet) {
List<Notification.Type> notifications = Notification.Type.Companion.getAsList();
boolean isChanged = false;
- for(Notification.Type type : notifications) {
- if(notificationFilter.contains(type) && !newSet.contains(type)) {
+ for (Notification.Type type : notifications) {
+ if (notificationFilter.contains(type) && !newSet.contains(type)) {
notificationFilter.remove(type);
isChanged = true;
- } else if(!notificationFilter.contains(type) && newSet.contains(type)) {
+ } else if (!notificationFilter.contains(type) && newSet.contains(type)) {
notificationFilter.add(type);
isChanged = true;
}
}
- if(isChanged) {
+ if (isChanged) {
saveNotificationsFilter();
fullyRefreshWithProgressBar(true);
}
-
}
private void loadNotificationsFilter() {
AccountEntity account = accountManager.getValue().getActiveAccount();
- if(account != null) {
+ if (account != null) {
notificationFilter.clear();
notificationFilter.addAll(
- NotificationTypeConverterKt.deserialize(account.getNotificationsFilter()));
+ NotificationTypeConverterKt.deserialize(account.getNotificationsFilter()));
}
}
private void saveNotificationsFilter() {
AccountEntity account = accountManager.getValue().getActiveAccount();
- if(account != null) {
+ if (account != null) {
account.setNotificationsFilter(
- NotificationTypeConverterKt.serialize(notificationFilter));
+ NotificationTypeConverterKt.serialize(notificationFilter));
accountManager.getValue().saveAccount(account);
}
}
@@ -963,19 +1087,26 @@ public class NotificationsFragment extends SFragment
@Override
public void onRespondToFollowRequest(boolean accept, String id, int position) {
Single<Relationship> request =
- accept ? mastodonApi.getValue().authorizeFollowRequestObservable(id) :
- mastodonApi.getValue().rejectFollowRequestObservable(id);
+ accept
+ ? mastodonApi.getValue().authorizeFollowRequestObservable(id)
+ : mastodonApi.getValue().rejectFollowRequestObservable(id);
request.observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((relationship) -> fullyRefreshWithProgressBar(true), (error) -> Log.e(TAG,
- String.format("Failed to %s account id %s", accept ? "accept" : "reject", id)));
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (relationship) -> fullyRefreshWithProgressBar(true),
+ (error) ->
+ Log.e(
+ TAG,
+ String.format(
+ "Failed to %s account id %s",
+ accept ? "accept" : "reject", id)));
}
@Override
public void onViewStatusForNotificationId(String notificationId) {
- for(Either<Placeholder, Notification> either : notifications) {
+ for (Either<Placeholder, Notification> either : notifications) {
Notification notification = either.asRightOrNull();
- if(notification != null && notification.getId().equals(notificationId)) {
+ if (notification != null && notification.getId().equals(notificationId)) {
super.viewThread(notification.getStatus());
return;
}
@@ -985,33 +1116,37 @@ public class NotificationsFragment extends SFragment
private void onPreferenceChanged(String key) {
SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(getContext());
+ PreferenceManager.getDefaultSharedPreferences(getContext());
- switch(key) {
- case "fabHide": {
- hideFab = sharedPreferences.getBoolean("fabHide", false);
- break;
- }
- case "mediaPreviewEnabled": {
- boolean enabled =
- accountManager.getValue().getActiveAccount().getMediaPreviewEnabled();
- if(enabled != adapter.isMediaPreviewEnabled()) {
- adapter.setMediaPreviewEnabled(enabled);
- fullyRefresh();
+ switch (key) {
+ case "fabHide":
+ {
+ hideFab = sharedPreferences.getBoolean("fabHide", false);
+ break;
}
- }
- case "showNotificationsFilter": {
- if(isAdded()) {
- showNotificationsFilter =
- sharedPreferences.getBoolean("showNotificationsFilter", true);
- updateFilterVisibility();
- fullyRefreshWithProgressBar(true);
+ case "mediaPreviewEnabled":
+ {
+ boolean enabled =
+ accountManager.getValue().getActiveAccount().getMediaPreviewEnabled();
+ if (enabled != adapter.isMediaPreviewEnabled()) {
+ adapter.setMediaPreviewEnabled(enabled);
+ fullyRefresh();
+ }
+ }
+ case "showNotificationsFilter":
+ {
+ if (isAdded()) {
+ showNotificationsFilter =
+ sharedPreferences.getBoolean("showNotificationsFilter", true);
+ updateFilterVisibility();
+ fullyRefreshWithProgressBar(true);
+ }
+ }
+ case PrefKeys.HIDE_MUTED_USERS:
+ {
+ withMuted = !sharedPreferences.getBoolean(PrefKeys.HIDE_MUTED_USERS, false);
+ fullyRefresh();
}
- }
- case PrefKeys.HIDE_MUTED_USERS: {
- withMuted = !sharedPreferences.getBoolean(PrefKeys.HIDE_MUTED_USERS, false);
- fullyRefresh();
- }
}
}
@@ -1024,12 +1159,16 @@ public class NotificationsFragment extends SFragment
private void removeAllByConversationId(String conversationId) {
// using iterator to safely remove items while iterating
Iterator<Either<Placeholder, Notification>> iterator = notifications.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Either<Placeholder, Notification> placeholderOrNotification = iterator.next();
Notification notification = placeholderOrNotification.asRightOrNull();
- if(notification != null && notification.getStatus() != null &&
- notification.getType() == Notification.Type.MENTION &&
- notification.getStatus().getConversationId().equalsIgnoreCase(conversationId)) {
+ if (notification != null
+ && notification.getStatus() != null
+ && notification.getType() == Notification.Type.MENTION
+ && notification
+ .getStatus()
+ .getConversationId()
+ .equalsIgnoreCase(conversationId)) {
iterator.remove();
}
}
@@ -1039,11 +1178,11 @@ public class NotificationsFragment extends SFragment
private void removeAllByAccountId(String accountId) {
// using iterator to safely remove items while iterating
Iterator<Either<Placeholder, Notification>> iterator = notifications.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Either<Placeholder, Notification> notification = iterator.next();
Notification maybeNotification = notification.asRightOrNull();
- if(maybeNotification != null &&
- maybeNotification.getAccount().getId().equals(accountId)) {
+ if (maybeNotification != null
+ && maybeNotification.getAccount().getId().equals(accountId)) {
iterator.remove();
}
}
@@ -1051,7 +1190,7 @@ public class NotificationsFragment extends SFragment
}
private void onLoadMore() {
- if(bottomId == null) {
+ if (bottomId == null) {
// already loaded everything
return;
}
@@ -1059,13 +1198,13 @@ public class NotificationsFragment extends SFragment
// Check for out-of-bounds when loading
// This is required to allow full-timeline reloads of collapsible statuses when the settings
// change.
- if(notifications.size() > 0) {
+ if (notifications.size() > 0) {
Either<Placeholder, Notification> last = notifications.get(notifications.size() - 1);
- if(last.isRight()) {
+ if (last.isRight()) {
final Placeholder placeholder = newPlaceholder();
notifications.add(new Either.Left<>(placeholder));
NotificationViewData viewData =
- new NotificationViewData.Placeholder(placeholder.id, true);
+ new NotificationViewData.Placeholder(placeholder.id, true);
notifications.setPairedItem(notifications.size() - 1, viewData);
updateAdapter();
}
@@ -1081,105 +1220,114 @@ public class NotificationsFragment extends SFragment
}
private void jumpToTop() {
- if(isAdded()) {
+ if (isAdded()) {
appBarOptions.setExpanded(true, false);
layoutManager.scrollToPosition(0);
scrollListener.reset();
}
}
- private void sendFetchNotificationsRequest(String fromId, String uptoId,
- final FetchEnd fetchEnd, final int pos)
- {
+ private void sendFetchNotificationsRequest(
+ String fromId, String uptoId, final FetchEnd fetchEnd, final int pos) {
/* If there is a fetch already ongoing, record however many fetches are requested and
* fulfill them after it's complete. */
- if(fetchEnd == FetchEnd.TOP && topLoading) {
+ if (fetchEnd == FetchEnd.TOP && topLoading) {
return;
}
- if(fetchEnd == FetchEnd.BOTTOM && bottomLoading) {
+ if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) {
return;
}
- if(fetchEnd == FetchEnd.TOP) {
+ if (fetchEnd == FetchEnd.TOP) {
topLoading = true;
}
- if(fetchEnd == FetchEnd.BOTTOM) {
+ if (fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = true;
}
- Call<List<Notification>> call = mastodonApi.getValue()
- .notifications(fromId, uptoId, LOAD_AT_ONCE,
- showNotificationsFilter ? notificationFilter : null, withMuted);
-
- call.enqueue(new Callback<List<Notification>>() {
- @Override
- public void onResponse(@NonNull Call<List<Notification>> call,
- @NonNull Response<List<Notification>> response)
- {
- if(response.isSuccessful()) {
- String linkHeader = response.headers().get("Link");
- onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos);
- } else {
- onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos);
- }
- }
+ Call<List<Notification>> call =
+ mastodonApi
+ .getValue()
+ .notifications(
+ fromId,
+ uptoId,
+ LOAD_AT_ONCE,
+ showNotificationsFilter ? notificationFilter : null,
+ withMuted);
+
+ call.enqueue(
+ new Callback<List<Notification>>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<List<Notification>> call,
+ @NonNull Response<List<Notification>> response) {
+ if (response.isSuccessful()) {
+ String linkHeader = response.headers().get("Link");
+ onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos);
+ } else {
+ onFetchNotificationsFailure(
+ new Exception(response.message()), fetchEnd, pos);
+ }
+ }
- @Override
- public void onFailure(@NonNull Call<List<Notification>> call, @NonNull Throwable t) {
- if(!call.isCanceled()) {
- onFetchNotificationsFailure((Exception) t, fetchEnd, pos);
- }
- }
- });
+ @Override
+ public void onFailure(
+ @NonNull Call<List<Notification>> call, @NonNull Throwable t) {
+ if (!call.isCanceled()) {
+ onFetchNotificationsFailure((Exception) t, fetchEnd, pos);
+ }
+ }
+ });
callList.add(call);
}
- private void onFetchNotificationsSuccess(List<Notification> notifications, String linkHeader,
- FetchEnd fetchEnd, int pos)
- {
+ private void onFetchNotificationsSuccess(
+ List<Notification> notifications, String linkHeader, FetchEnd fetchEnd, int pos) {
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next");
String fromId = null;
- if(next != null) {
+ if (next != null) {
fromId = next.uri.getQueryParameter("max_id");
}
- switch(fetchEnd) {
- case TOP: {
- update(notifications, this.notifications.isEmpty() ? fromId : null);
- break;
- }
- case MIDDLE: {
- replacePlaceholderWithNotifications(notifications, pos);
- break;
- }
- case BOTTOM: {
-
- if(!this.notifications.isEmpty() &&
- !this.notifications.get(this.notifications.size() - 1).isRight()) {
- this.notifications.remove(this.notifications.size() - 1);
- updateAdapter();
+ switch (fetchEnd) {
+ case TOP:
+ {
+ update(notifications, this.notifications.isEmpty() ? fromId : null);
+ break;
}
-
- if(adapter.getItemCount() > 1) {
- addItems(notifications, fromId);
- } else {
- update(notifications, fromId);
+ case MIDDLE:
+ {
+ replacePlaceholderWithNotifications(notifications, pos);
+ break;
}
+ case BOTTOM:
+ {
+ if (!this.notifications.isEmpty()
+ && !this.notifications.get(this.notifications.size() - 1).isRight()) {
+ this.notifications.remove(this.notifications.size() - 1);
+ updateAdapter();
+ }
- break;
- }
+ if (adapter.getItemCount() > 1) {
+ addItems(notifications, fromId);
+ } else {
+ update(notifications, fromId);
+ }
+
+ break;
+ }
}
saveNewestNotificationId(notifications);
- if(fetchEnd == FetchEnd.TOP) {
+ if (fetchEnd == FetchEnd.TOP) {
topLoading = false;
}
- if(fetchEnd == FetchEnd.BOTTOM) {
+ if (fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = false;
}
- if(notifications.size() == 0 && adapter.getItemCount() == 0) {
+ if (notifications.size() == 0 && adapter.getItemCount() == 0) {
this.statusView.setVisibility(View.VISIBLE);
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
} else {
@@ -1192,37 +1340,43 @@ public class NotificationsFragment extends SFragment
private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) {
swipeRefreshLayout.setRefreshing(false);
- if(fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
+ if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
Placeholder placeholder = notifications.get(position).asLeft();
NotificationViewData placeholderVD =
- new NotificationViewData.Placeholder(placeholder.id, false);
+ new NotificationViewData.Placeholder(placeholder.id, false);
notifications.setPairedItem(position, placeholderVD);
updateAdapter();
- } else if(this.notifications.isEmpty()) {
+ } else if (this.notifications.isEmpty()) {
this.statusView.setVisibility(View.VISIBLE);
swipeRefreshLayout.setEnabled(false);
this.showingError = true;
- if(exception instanceof IOException) {
- this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
- this.progressBar.setVisibility(View.VISIBLE);
- this.onRefresh();
- return Unit.INSTANCE;
- });
+ if (exception instanceof IOException) {
+ this.statusView.setup(
+ R.drawable.elephant_offline,
+ R.string.error_network,
+ __ -> {
+ this.progressBar.setVisibility(View.VISIBLE);
+ this.onRefresh();
+ return Unit.INSTANCE;
+ });
} else {
- this.statusView.setup(R.drawable.elephant_error, R.string.error_generic, __ -> {
- this.progressBar.setVisibility(View.VISIBLE);
- this.onRefresh();
- return Unit.INSTANCE;
- });
+ this.statusView.setup(
+ R.drawable.elephant_error,
+ R.string.error_generic,
+ __ -> {
+ this.progressBar.setVisibility(View.VISIBLE);
+ this.onRefresh();
+ return Unit.INSTANCE;
+ });
}
updateFilterVisibility();
}
Log.e(TAG, "Fetch failure: " + exception.getMessage());
- if(fetchEnd == FetchEnd.TOP) {
+ if (fetchEnd == FetchEnd.TOP) {
topLoading = false;
}
- if(fetchEnd == FetchEnd.BOTTOM) {
+ if (fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = false;
}
@@ -1232,16 +1386,16 @@ public class NotificationsFragment extends SFragment
private void saveNewestNotificationId(List<Notification> notifications) {
AccountEntity account = accountManager.getValue().getActiveAccount();
- if(account != null) {
+ if (account != null) {
String lastNotificationId = account.getLastNotificationId();
- for(Notification noti : notifications) {
- if(isLessThan(lastNotificationId, noti.getId())) {
+ for (Notification noti : notifications) {
+ if (isLessThan(lastNotificationId, noti.getId())) {
lastNotificationId = noti.getId();
}
}
- if(!account.getLastNotificationId().equals(lastNotificationId)) {
+ if (!account.getLastNotificationId().equals(lastNotificationId)) {
Log.d(TAG, "saving newest noti id: " + lastNotificationId);
account.setLastNotificationId(lastNotificationId);
accountManager.getValue().saveAccount(account);
@@ -1250,25 +1404,25 @@ public class NotificationsFragment extends SFragment
}
private void update(@Nullable List<Notification> newNotifications, @Nullable String fromId) {
- if(ListUtils.isEmpty(newNotifications)) {
+ if (ListUtils.isEmpty(newNotifications)) {
updateAdapter();
return;
}
- if(fromId != null) {
+ if (fromId != null) {
bottomId = fromId;
}
List<Either<Placeholder, Notification>> liftedNew = liftNotificationList(newNotifications);
- if(notifications.isEmpty()) {
+ if (notifications.isEmpty()) {
notifications.addAll(liftedNew);
} else {
int index = notifications.indexOf(liftedNew.get(newNotifications.size() - 1));
- for(int i = 0; i < index; i++) {
+ for (int i = 0; i < index; i++) {
notifications.remove(0);
}
int newIndex = liftedNew.indexOf(notifications.get(0));
- if(newIndex == -1) {
- if(index == -1 && liftedNew.size() >= LOAD_AT_ONCE) {
+ if (newIndex == -1) {
+ if (index == -1 && liftedNew.size() >= LOAD_AT_ONCE) {
liftedNew.add(new Either.Left<>(newPlaceholder()));
}
notifications.addAll(0, liftedNew);
@@ -1281,13 +1435,13 @@ public class NotificationsFragment extends SFragment
private void addItems(List<Notification> newNotifications, @Nullable String fromId) {
bottomId = fromId;
- if(ListUtils.isEmpty(newNotifications)) {
+ if (ListUtils.isEmpty(newNotifications)) {
return;
}
int end = notifications.size();
List<Either<Placeholder, Notification>> liftedNew = liftNotificationList(newNotifications);
Either<Placeholder, Notification> last = notifications.get(end - 1);
- if(last != null && liftedNew.indexOf(last) == -1) {
+ if (last != null && liftedNew.indexOf(last) == -1) {
notifications.addAll(liftedNew);
updateAdapter();
}
@@ -1297,7 +1451,7 @@ public class NotificationsFragment extends SFragment
// Remove placeholder
notifications.remove(pos);
- if(ListUtils.isEmpty(newNotifications)) {
+ if (ListUtils.isEmpty(newNotifications)) {
updateAdapter();
return;
}
@@ -1307,7 +1461,7 @@ public class NotificationsFragment extends SFragment
// If we fetched less posts than in the limit, it means that the hole is not filled
// If we fetched at least as much it means that there are more posts to load and we should
// insert new placeholder
- if(newNotifications.size() >= LOAD_AT_ONCE) {
+ if (newNotifications.size() >= LOAD_AT_ONCE) {
liftedNew.add(new Either.Left<>(newPlaceholder()));
}
@@ -1316,7 +1470,7 @@ public class NotificationsFragment extends SFragment
}
private final Function1<Notification, Either<Placeholder, Notification>> notificationLifter =
- Either.Right::new;
+ Either.Right::new;
private List<Either<Placeholder, Notification>> liftNotificationList(List<Notification> list) {
return CollectionsKt.map(list, notificationLifter);
@@ -1324,7 +1478,7 @@ public class NotificationsFragment extends SFragment
private void fullyRefreshWithProgressBar(boolean isShow) {
resetNotificationsLoad();
- if(isShow) {
+ if (isShow) {
progressBar.setVisibility(View.VISIBLE);
statusView.setVisibility(View.GONE);
}
@@ -1338,13 +1492,15 @@ public class NotificationsFragment extends SFragment
@Nullable
private Pair<Integer, Notification> findReplyPosition(@NonNull String statusId) {
- for(int i = 0; i < notifications.size(); i++) {
+ for (int i = 0; i < notifications.size(); i++) {
Notification notification = notifications.get(i).asRightOrNull();
- if(notification != null && notification.getStatus() != null &&
- notification.getType() == Notification.Type.MENTION &&
- (statusId.equals(notification.getStatus().getId()) ||
- (notification.getStatus().getReblog() != null &&
- statusId.equals(notification.getStatus().getReblog().getId())))) {
+ if (notification != null
+ && notification.getStatus() != null
+ && notification.getType() == Notification.Type.MENTION
+ && (statusId.equals(notification.getStatus().getId())
+ || (notification.getStatus().getReblog() != null
+ && statusId.equals(
+ notification.getStatus().getReblog().getId())))) {
return new Pair<>(i, notification);
}
}
@@ -1355,94 +1511,94 @@ public class NotificationsFragment extends SFragment
differ.submitList(notifications.getPairedCopy());
}
- private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
- @Override
- public void onInserted(int position, int count) {
- if(isAdded()) {
- adapter.notifyItemRangeInserted(position, count);
- Context context = getContext();
- // scroll up when new items at the top are loaded while being at the start
- // https://github.com/tuskyapp/Tusky/pull/1905#issuecomment-677819724
- if(position == 0 && context != null && adapter.getItemCount() != count) {
- recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
+ private final ListUpdateCallback listUpdateCallback =
+ new ListUpdateCallback() {
+ @Override
+ public void onInserted(int position, int count) {
+ if (isAdded()) {
+ adapter.notifyItemRangeInserted(position, count);
+ Context context = getContext();
+ // scroll up when new items at the top are loaded while being at the start
+ // https://github.com/tuskyapp/Tusky/pull/1905#issuecomment-677819724
+ if (position == 0 && context != null && adapter.getItemCount() != count) {
+ recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
+ }
+ }
}
- }
- }
- @Override
- public void onRemoved(int position, int count) {
- adapter.notifyItemRangeRemoved(position, count);
- }
+ @Override
+ public void onRemoved(int position, int count) {
+ adapter.notifyItemRangeRemoved(position, count);
+ }
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- adapter.notifyItemMoved(fromPosition, toPosition);
- }
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ adapter.notifyItemMoved(fromPosition, toPosition);
+ }
- @Override
- public void onChanged(int position, int count, Object payload) {
- adapter.notifyItemRangeChanged(position, count, payload);
- }
- };
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ adapter.notifyItemRangeChanged(position, count, payload);
+ }
+ };
private final AsyncListDiffer<NotificationViewData> differ =
- new AsyncListDiffer<>(listUpdateCallback,
- new AsyncDifferConfig.Builder<>(diffCallback).build());
+ new AsyncListDiffer<>(
+ listUpdateCallback, new AsyncDifferConfig.Builder<>(diffCallback).build());
private final NotificationsAdapter.AdapterDataSource<NotificationViewData> dataSource =
- new NotificationsAdapter.AdapterDataSource<NotificationViewData>() {
- @Override
- public int getItemCount() {
- return differ.getCurrentList().size();
- }
+ new NotificationsAdapter.AdapterDataSource<NotificationViewData>() {
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
- @Override
- public NotificationViewData getItemAt(int pos) {
- return differ.getCurrentList().get(pos);
- }
- };
+ @Override
+ public NotificationViewData getItemAt(int pos) {
+ return differ.getCurrentList().get(pos);
+ }
+ };
private static final DiffUtil.ItemCallback<NotificationViewData> diffCallback =
- new DiffUtil.ItemCallback<NotificationViewData>() {
+ new DiffUtil.ItemCallback<NotificationViewData>() {
- @Override
- public boolean areItemsTheSame(NotificationViewData oldItem,
- NotificationViewData newItem)
- {
- return oldItem.getViewDataId() == newItem.getViewDataId();
- }
+ @Override
+ public boolean areItemsTheSame(
+ NotificationViewData oldItem, NotificationViewData newItem) {
+ return oldItem.getViewDataId() == newItem.getViewDataId();
+ }
- @Override
- public boolean areContentsTheSame(@NonNull NotificationViewData oldItem,
- @NonNull NotificationViewData newItem)
- {
- return false;
- }
+ @Override
+ public boolean areContentsTheSame(
+ @NonNull NotificationViewData oldItem,
+ @NonNull NotificationViewData newItem) {
+ return false;
+ }
- @Nullable
- @Override
- public Object getChangePayload(@NonNull NotificationViewData oldItem,
- @NonNull NotificationViewData newItem)
- {
- if(oldItem.deepEquals(newItem)) {
- //If items are equal - update timestamp only
- return Collections.singletonList(StatusBaseViewHolder.Key.KEY_CREATED);
- } else
- // If items are different - update a whole view holder
- {
- return null;
+ @Nullable
+ @Override
+ public Object getChangePayload(
+ @NonNull NotificationViewData oldItem,
+ @NonNull NotificationViewData newItem) {
+ if (oldItem.deepEquals(newItem)) {
+ // If items are equal - update timestamp only
+ return Collections.singletonList(StatusBaseViewHolder.Key.KEY_CREATED);
+ } else
+ // If items are different - update a whole view holder
+ {
+ return null;
+ }
}
- }
- };
+ };
@Override
public void onResume() {
super.onResume();
String rawAccountNotificationFilter =
- accountManager.getValue().getActiveAccount().getNotificationsFilter();
+ accountManager.getValue().getActiveAccount().getNotificationsFilter();
Set<Notification.Type> accountNotificationFilter =
- NotificationTypeConverterKt.deserialize(rawAccountNotificationFilter);
- if(!notificationFilter.equals(accountNotificationFilter)) {
+ NotificationTypeConverterKt.deserialize(rawAccountNotificationFilter);
+ if (!notificationFilter.equals(accountNotificationFilter)) {
loadNotificationsFilter();
fullyRefreshWithProgressBar(true);
}
@@ -1450,20 +1606,19 @@ public class NotificationsFragment extends SFragment
}
/**
- * Start to update adapter every minute to refresh timestamp
- * If setting absoluteTimeView is false
- * Auto dispose observable on pause
+ * Start to update adapter every minute to refresh timestamp If setting absoluteTimeView is
+ * false Auto dispose observable on pause
*/
private void startUpdateTimestamp() {
SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
+ PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
- if(!useAbsoluteTime) {
- Observable.interval(1, TimeUnit.MINUTES).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_PAUSE)))
- .subscribe(interval -> updateAdapter());
+ if (!useAbsoluteTime) {
+ Observable.interval(1, TimeUnit.MINUTES)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_PAUSE)))
+ .subscribe(interval -> updateAdapter());
}
-
}
@Override
@@ -1473,12 +1628,17 @@ public class NotificationsFragment extends SFragment
private void setEmojiReactForStatus(int position, Status newStatus) {
NotificationViewData.Concrete viewdata =
- (NotificationViewData.Concrete) notifications.getPairedItem(position);
+ (NotificationViewData.Concrete) notifications.getPairedItem(position);
NotificationViewData.Concrete newViewData =
- new NotificationViewData.Concrete(viewdata.getType(), viewdata.getId(),
- viewdata.getAccount(), ViewDataUtils.statusToViewData(newStatus, false, false),
- viewdata.getEmoji(), viewdata.getEmojiUrl(), viewdata.getTarget());
+ new NotificationViewData.Concrete(
+ viewdata.getType(),
+ viewdata.getId(),
+ viewdata.getAccount(),
+ ViewDataUtils.statusToViewData(newStatus, false, false),
+ viewdata.getEmoji(),
+ viewdata.getEmojiUrl(),
+ viewdata.getTarget());
notifications.setPairedItem(position, newViewData);
updateAdapter();
@@ -1486,33 +1646,38 @@ public class NotificationsFragment extends SFragment
private void handleEmojiReactEvent(EmojiReactEvent event) {
Pair<Integer, Notification> posAndNotification =
- findReplyPosition(event.getNewStatus().getActionableId());
- if(posAndNotification == null) {
+ findReplyPosition(event.getNewStatus().getActionableId());
+ if (posAndNotification == null) {
return;
}
//noinspection ConstantConditions
setEmojiReactForStatus(posAndNotification.first, event.getNewStatus());
}
-
@Override
public void onEmojiReact(final boolean react, final String emoji, final String statusId) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(statusId);
- if(posAndNotification == null) {
+ if (posAndNotification == null) {
return;
}
- timelineCases.getValue().react(emoji, statusId, react)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setEmojiReactForStatus(posAndNotification.first, newStatus),
- (t) -> Log.d(TAG, "Failed to react with " + emoji + " on status: " + statusId, t));
-
+ timelineCases
+ .getValue()
+ .react(emoji, statusId, react)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setEmojiReactForStatus(posAndNotification.first, newStatus),
+ (t) ->
+ Log.d(
+ TAG,
+ "Failed to react with " + emoji + " on status: " + statusId,
+ t));
}
@Override
- public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji,
- final String statusId)
- {
+ public void onEmojiReactMenu(
+ @NonNull View view, final EmojiReaction emoji, final String statusId) {
super.emojiReactMenu(statusId, emoji, view, this);
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
index 8b608da..b10a2f7 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
@@ -15,6 +15,10 @@
package com.keylesspalace.tusky.fragment;
+import static com.uber.autodispose.AutoDispose.autoDisposable;
+import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.Manifest;
import android.app.DownloadManager;
import android.content.ClipData;
@@ -64,8 +68,6 @@ import com.keylesspalace.tusky.settings.PrefKeys;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.view.MuteAccountDialog;
import com.keylesspalace.tusky.viewdata.AttachmentViewData;
-import static com.uber.autodispose.AutoDispose.autoDisposable;
-import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.LinkedHashSet;
@@ -75,7 +77,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kotlin.Lazy;
import kotlin.Unit;
-import static org.koin.java.KoinJavaComponent.inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@@ -115,7 +116,7 @@ public abstract class SFragment extends BaseFragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- if(context instanceof BottomSheetActivity) {
+ if (context instanceof BottomSheetActivity) {
bottomSheetActivity = (BottomSheetActivity) context;
} else {
throw new IllegalStateException("Fragment must be attached to a BottomSheetActivity!");
@@ -123,7 +124,7 @@ public abstract class SFragment extends BaseFragment {
}
protected void openReblog(@Nullable final Status status) {
- if(status == null) {
+ if (status == null) {
return;
}
bottomSheetActivity.viewAccount(status.getAccount().getId());
@@ -156,10 +157,10 @@ public abstract class SFragment extends BaseFragment {
mentionedUsernames.add(actionableStatus.getAccount().getUsername());
String loggedInUsername = null;
AccountEntity activeAccount = accountManager.getValue().getActiveAccount();
- if(activeAccount != null) {
+ if (activeAccount != null) {
loggedInUsername = activeAccount.getUsername();
}
- for(Status.Mention mention : mentions) {
+ for (Status.Mention mention : mentions) {
mentionedUsernames.add(mention.getUsername());
}
mentionedUsernames.remove(loggedInUsername);
@@ -175,9 +176,11 @@ public abstract class SFragment extends BaseFragment {
getActivity().startActivity(intent);
}
- protected void emojiReactMenu(@NonNull final String statusId,
- @NonNull final EmojiReaction reaction, View view, final StatusActionListener listener)
- {
+ protected void emojiReactMenu(
+ @NonNull final String statusId,
+ @NonNull final EmojiReaction reaction,
+ View view,
+ final StatusActionListener listener) {
PopupMenu popup = new PopupMenu(getContext(), view);
popup.inflate(R.menu.emoji_reaction_more);
@@ -185,23 +188,29 @@ public abstract class SFragment extends BaseFragment {
menu.findItem(R.id.emoji_react).setVisible(!reaction.getMe());
menu.findItem(R.id.emoji_unreact).setVisible(reaction.getMe());
- popup.setOnMenuItemClickListener(item -> {
- switch(item.getItemId()) {
- case R.id.emoji_react:
- listener.onEmojiReact(true, reaction.getName(), statusId);
- return true;
- case R.id.emoji_unreact:
- listener.onEmojiReact(false, reaction.getName(), statusId);
- return true;
- case R.id.emoji_reacted_by:
- Intent intent = AccountListActivity.newIntent(getContext(),
- AccountListActivity.Type.REACTED, statusId, reaction.getName());
- ((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
-
- return true;
- }
- return false;
- });
+ popup.setOnMenuItemClickListener(
+ item -> {
+ switch (item.getItemId()) {
+ case R.id.emoji_react:
+ listener.onEmojiReact(true, reaction.getName(), statusId);
+ return true;
+ case R.id.emoji_unreact:
+ listener.onEmojiReact(false, reaction.getName(), statusId);
+ return true;
+ case R.id.emoji_reacted_by:
+ Intent intent =
+ AccountListActivity.newIntent(
+ getContext(),
+ AccountListActivity.Type.REACTED,
+ statusId,
+ reaction.getName());
+ ((BaseActivity) getActivity())
+ .startActivityWithSlideInAnimation(intent);
+
+ return true;
+ }
+ return false;
+ });
popup.show();
}
@@ -215,52 +224,58 @@ public abstract class SFragment extends BaseFragment {
String loggedInAccountId = null;
AccountEntity activeAccount = accountManager.getValue().getActiveAccount();
- if(activeAccount != null) {
+ if (activeAccount != null) {
loggedInAccountId = activeAccount.getAccountId();
}
PopupMenu popup = new PopupMenu(getContext(), view);
// Give a different menu depending on whether this is the user's own toot or not.
- if(loggedInAccountId == null || !loggedInAccountId.equals(accountId)) {
+ if (loggedInAccountId == null || !loggedInAccountId.equals(accountId)) {
popup.inflate(R.menu.status_more);
Menu menu = popup.getMenu();
menu.findItem(R.id.status_download_media)
- .setVisible(!status.getAttachments().isEmpty());
+ .setVisible(!status.getAttachments().isEmpty());
} else {
popup.inflate(R.menu.status_more_for_user);
Menu menu = popup.getMenu();
- switch(status.getVisibility()) {
+ switch (status.getVisibility()) {
case PUBLIC:
- case UNLISTED: {
- final String textId =
- getString(status.isPinned() ? R.string.unpin_action : R.string.pin_action);
- menu.add(0, R.id.pin, 1, textId);
- break;
- }
- case PRIVATE: {
- boolean reblogged = status.getReblogged();
- if(status.getReblog() != null) {
- reblogged = status.getReblog().getReblogged();
+ case UNLISTED:
+ {
+ final String textId =
+ getString(
+ status.isPinned()
+ ? R.string.unpin_action
+ : R.string.pin_action);
+ menu.add(0, R.id.pin, 1, textId);
+ break;
+ }
+ case PRIVATE:
+ {
+ boolean reblogged = status.getReblogged();
+ if (status.getReblog() != null) {
+ reblogged = status.getReblog().getReblogged();
+ }
+ menu.findItem(R.id.status_reblog_private).setVisible(!reblogged);
+ menu.findItem(R.id.status_unreblog_private).setVisible(reblogged);
+ break;
}
- menu.findItem(R.id.status_reblog_private).setVisible(!reblogged);
- menu.findItem(R.id.status_unreblog_private).setVisible(reblogged);
- break;
- }
}
}
Menu menu = popup.getMenu();
MenuItem openAsItem = menu.findItem(R.id.status_open_as);
- switch(accounts.size()) {
+ switch (accounts.size()) {
case 0:
case 1:
openAsItem.setVisible(false);
break;
case 2:
- for(AccountEntity account : accounts) {
- if(account != activeAccount) {
- openAsTitle = String.format(getString(R.string.action_open_as),
- account.getFullName());
+ for (AccountEntity account : accounts) {
+ if (account != activeAccount) {
+ openAsTitle =
+ String.format(
+ getString(R.string.action_open_as), account.getFullName());
break;
}
}
@@ -272,10 +287,10 @@ public abstract class SFragment extends BaseFragment {
openAsItem.setTitle(openAsTitle);
// maybe not a best check
- if(status.getPleroma() != null) {
+ if (status.getPleroma() != null) {
boolean showMute = true; // predict state
- if(status.isThreadMuted() == true) {
+ if (status.isThreadMuted() == true) {
showMute = false;
}
@@ -285,121 +300,151 @@ public abstract class SFragment extends BaseFragment {
menu.findItem(R.id.status_unmute_conversation).setVisible(!showMute);
}
- popup.setOnMenuItemClickListener(item -> {
- switch(item.getItemId()) {
- case R.id.status_share_content: {
- Status statusToShare = status;
- if(statusToShare.getReblog() != null) {
- statusToShare = statusToShare.getReblog();
+ popup.setOnMenuItemClickListener(
+ item -> {
+ switch (item.getItemId()) {
+ case R.id.status_share_content:
+ {
+ Status statusToShare = status;
+ if (statusToShare.getReblog() != null) {
+ statusToShare = statusToShare.getReblog();
+ }
+
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+
+ String stringToShare =
+ statusToShare.getAccount().getUsername()
+ + " - "
+ + statusToShare.getContent().toString();
+ sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
+ sendIntent.putExtra(Intent.EXTRA_SUBJECT, statusUrl);
+ 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_link_to)));
+ return true;
+ }
+ case R.id.status_copy_link:
+ {
+ ClipboardManager clipboard =
+ (ClipboardManager)
+ getActivity()
+ .getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText(null, statusUrl);
+ clipboard.setPrimaryClip(clip);
+ return true;
+ }
+ case R.id.status_open_in_web:
+ {
+ LinkHelper.openLinkInBrowser(Uri.parse(statusUrl), getContext());
+ return true;
+ }
+ case R.id.status_open_as:
+ {
+ showOpenAsDialog(statusUrl, item.getTitle());
+ return true;
+ }
+ case R.id.status_download_media:
+ {
+ requestDownloadAllMedia(status);
+ return true;
+ }
+ case R.id.status_mute:
+ {
+ onMute(accountId, accountUsername);
+ return true;
+ }
+ case R.id.status_block:
+ {
+ onBlock(accountId, accountUsername);
+ return true;
+ }
+ case R.id.status_report:
+ {
+ openReportPage(accountId, accountUsername, id);
+ return true;
+ }
+ case R.id.status_mute_conversation:
+ {
+ timelineCases.getValue().muteConversation(status, true);
+ return true;
+ }
+ case R.id.status_unmute_conversation:
+ {
+ timelineCases.getValue().muteConversation(status, false);
+ return true;
+ }
+ case R.id.status_unreblog_private:
+ {
+ onReblog(false, position);
+ return true;
+ }
+ case R.id.status_reblog_private:
+ {
+ onReblog(true, position);
+ return true;
+ }
+ case R.id.status_delete:
+ {
+ showConfirmDeleteDialog(id, position);
+ return true;
+ }
+ case R.id.pin:
+ {
+ timelineCases.getValue().pin(status, !status.isPinned());
+ return true;
+ }
}
-
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
-
- String stringToShare = statusToShare.getAccount().getUsername() + " - " +
- statusToShare.getContent().toString();
- sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
- sendIntent.putExtra(Intent.EXTRA_SUBJECT, statusUrl);
- 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_link_to)));
- return true;
- }
- case R.id.status_copy_link: {
- ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(
- Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText(null, statusUrl);
- clipboard.setPrimaryClip(clip);
- return true;
- }
- case R.id.status_open_in_web: {
- LinkHelper.openLinkInBrowser(Uri.parse(statusUrl), getContext());
- return true;
- }
- case R.id.status_open_as: {
- showOpenAsDialog(statusUrl, item.getTitle());
- return true;
- }
- case R.id.status_download_media: {
- requestDownloadAllMedia(status);
- return true;
- }
- case R.id.status_mute: {
- onMute(accountId, accountUsername);
- return true;
- }
- case R.id.status_block: {
- onBlock(accountId, accountUsername);
- return true;
- }
- case R.id.status_report: {
- openReportPage(accountId, accountUsername, id);
- return true;
- }
- case R.id.status_mute_conversation: {
- timelineCases.getValue().muteConversation(status, true);
- return true;
- }
- case R.id.status_unmute_conversation: {
- timelineCases.getValue().muteConversation(status, false);
- return true;
- }
- case R.id.status_unreblog_private: {
- onReblog(false, position);
- return true;
- }
- case R.id.status_reblog_private: {
- onReblog(true, position);
- return true;
- }
- case R.id.status_delete: {
- showConfirmDeleteDialog(id, position);
- return true;
- }
- case R.id.pin: {
- timelineCases.getValue().pin(status, !status.isPinned());
- return true;
- }
- }
- return false;
- });
+ return false;
+ });
popup.show();
}
private void onMute(String accountId, String accountUsername) {
- MuteAccountDialog.showMuteAccountDialog(this.getActivity(), accountUsername,
- (notifications, duration) -> {
- timelineCases.getValue().mute(accountId, notifications, duration);
- return Unit.INSTANCE;
- });
+ MuteAccountDialog.showMuteAccountDialog(
+ this.getActivity(),
+ accountUsername,
+ (notifications, duration) -> {
+ timelineCases.getValue().mute(accountId, notifications, duration);
+ return Unit.INSTANCE;
+ });
}
private void onBlock(String accountId, String accountUsername) {
- new AlertDialog.Builder(requireContext()).setMessage(
- getString(R.string.dialog_block_warning, accountUsername))
- .setPositiveButton(android.R.string.ok,
- (__, ___) -> timelineCases.getValue().block(accountId))
- .setNegativeButton(android.R.string.cancel, null).show();
+ new AlertDialog.Builder(requireContext())
+ .setMessage(getString(R.string.dialog_block_warning, accountUsername))
+ .setPositiveButton(
+ android.R.string.ok, (__, ___) -> timelineCases.getValue().block(accountId))
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
}
private static boolean accountIsInMentions(AccountEntity account, Status.Mention[] mentions) {
- if(account == null) {
+ if (account == null) {
return false;
}
- for(Status.Mention mention : mentions) {
- if(account.getUsername().equals(mention.getUsername())) {
+ for (Status.Mention mention : mentions) {
+ if (account.getUsername().equals(mention.getUsername())) {
Uri uri = Uri.parse(mention.getUrl());
- if(uri != null && account.getDomain().equals(uri.getHost())) {
+ if (uri != null && account.getDomain().equals(uri.getHost())) {
return true;
}
}
@@ -411,31 +456,34 @@ public abstract class SFragment extends BaseFragment {
final Status actionable = status.getActionableStatus();
final Attachment active = actionable.getAttachments().get(urlIndex);
Attachment.Type type = active.getType();
- switch(type) {
+ switch (type) {
case GIFV:
case VIDEO:
case IMAGE:
- case AUDIO: {
- final List<AttachmentViewData> attachments = AttachmentViewData.list(actionable);
- final Intent intent =
- ViewMediaActivity.newIntent(getContext(), attachments, urlIndex);
- if(view != null) {
- String url = active.getUrl();
- ViewCompat.setTransitionName(view, url);
- ActivityOptionsCompat options =
- ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view,
- url);
- startActivity(intent, options.toBundle());
- } else {
- startActivity(intent);
+ case AUDIO:
+ {
+ final List<AttachmentViewData> attachments =
+ AttachmentViewData.list(actionable);
+ final Intent intent =
+ ViewMediaActivity.newIntent(getContext(), attachments, urlIndex);
+ if (view != null) {
+ String url = active.getUrl();
+ ViewCompat.setTransitionName(view, url);
+ ActivityOptionsCompat options =
+ ActivityOptionsCompat.makeSceneTransitionAnimation(
+ getActivity(), view, url);
+ startActivity(intent, options.toBundle());
+ } else {
+ startActivity(intent);
+ }
+ break;
}
- break;
- }
default:
- case UNKNOWN: {
- LinkHelper.openLink(active.getUrl(), getContext());
- break;
- }
+ case UNKNOWN:
+ {
+ LinkHelper.openLink(active.getUrl(), getContext());
+ break;
+ }
}
}
@@ -447,60 +495,96 @@ public abstract class SFragment extends BaseFragment {
protected void openReportPage(String accountId, String accountUsername, String statusId) {
startActivity(
- ReportActivity.getIntent(requireContext(), accountId, accountUsername, statusId));
+ ReportActivity.getIntent(requireContext(), accountId, accountUsername, statusId));
}
protected void showConfirmDeleteDialog(final String id, final int position) {
- new AlertDialog.Builder(getActivity()).setMessage(R.string.dialog_delete_toot_warning)
- .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
- timelineCases.getValue().delete(id).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe(deletedStatus -> {
- }, error -> {
- Log.w("SFragment", "error deleting status", error);
- Toast.makeText(getContext(), R.string.error_generic, Toast.LENGTH_SHORT)
- .show();
- });
- removeItem(position);
- }).setNegativeButton(android.R.string.cancel, null).show();
+ new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.dialog_delete_toot_warning)
+ .setPositiveButton(
+ android.R.string.ok,
+ (dialogInterface, i) -> {
+ timelineCases
+ .getValue()
+ .delete(id)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ deletedStatus -> {},
+ error -> {
+ Log.w("SFragment", "error deleting status", error);
+ Toast.makeText(
+ getContext(),
+ R.string.error_generic,
+ Toast.LENGTH_SHORT)
+ .show();
+ });
+ removeItem(position);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
}
private void showConfirmEditDialog(final String id, final int position, final Status status) {
- if(getActivity() == null) {
+ if (getActivity() == null) {
return;
}
- new AlertDialog.Builder(getActivity()).setMessage(R.string.dialog_redraft_toot_warning)
- .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
- timelineCases.getValue().delete(id).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe(deletedStatus -> {
- removeItem(position);
-
- if(deletedStatus.isEmpty()) {
- deletedStatus = status.toDeletedStatus();
- }
- ComposeOptions composeOptions = new ComposeOptions();
- composeOptions.setTootText(deletedStatus.getText());
- composeOptions.setInReplyToId(deletedStatus.getInReplyToId());
- composeOptions.setVisibility(deletedStatus.getVisibility());
- composeOptions.setContentWarning(deletedStatus.getSpoilerText());
- composeOptions.setMediaAttachments(deletedStatus.getAttachments());
- composeOptions.setSensitive(deletedStatus.getSensitive());
- composeOptions.setModifiedInitialState(true);
- if(deletedStatus.getPoll() != null) {
- composeOptions.setPoll(
- deletedStatus.getPoll().toNewPoll(deletedStatus.getCreatedAt()));
- }
-
- Intent intent = ComposeActivity.startIntent(getContext(), composeOptions);
- startActivity(intent);
- }, error -> {
- Log.w("SFragment", "error deleting status", error);
- Toast.makeText(getContext(), R.string.error_generic, Toast.LENGTH_SHORT)
- .show();
- });
-
- }).setNegativeButton(android.R.string.cancel, null).show();
+ new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.dialog_redraft_toot_warning)
+ .setPositiveButton(
+ android.R.string.ok,
+ (dialogInterface, i) -> {
+ timelineCases
+ .getValue()
+ .delete(id)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ deletedStatus -> {
+ removeItem(position);
+
+ if (deletedStatus.isEmpty()) {
+ deletedStatus = status.toDeletedStatus();
+ }
+ ComposeOptions composeOptions =
+ new ComposeOptions();
+ composeOptions.setTootText(deletedStatus.getText());
+ composeOptions.setInReplyToId(
+ deletedStatus.getInReplyToId());
+ composeOptions.setVisibility(
+ deletedStatus.getVisibility());
+ composeOptions.setContentWarning(
+ deletedStatus.getSpoilerText());
+ composeOptions.setMediaAttachments(
+ deletedStatus.getAttachments());
+ composeOptions.setSensitive(
+ deletedStatus.getSensitive());
+ composeOptions.setModifiedInitialState(true);
+ if (deletedStatus.getPoll() != null) {
+ composeOptions.setPoll(
+ deletedStatus
+ .getPoll()
+ .toNewPoll(
+ deletedStatus
+ .getCreatedAt()));
+ }
+
+ Intent intent =
+ ComposeActivity.startIntent(
+ getContext(), composeOptions);
+ startActivity(intent);
+ },
+ error -> {
+ Log.w("SFragment", "error deleting status", error);
+ Toast.makeText(
+ getContext(),
+ R.string.error_generic,
+ Toast.LENGTH_SHORT)
+ .show();
+ });
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
}
private void openAsAccount(String statusUrl, AccountEntity account) {
@@ -514,19 +598,19 @@ public abstract class SFragment extends BaseFragment {
private void showOpenAsDialog(String statusUrl, CharSequence dialogTitle) {
BaseActivity activity = (BaseActivity) getActivity();
- activity.showAccountChooserDialog(dialogTitle, false,
- account -> openAsAccount(statusUrl, account));
+ activity.showAccountChooserDialog(
+ dialogTitle, false, account -> openAsAccount(statusUrl, account));
}
private void downloadAllMedia(Status status) {
Toast.makeText(getContext(), R.string.downloading_media, Toast.LENGTH_SHORT).show();
- for(Attachment attachment : status.getAttachments()) {
+ for (Attachment attachment : status.getAttachments()) {
String url = attachment.getUrl();
Uri uri = Uri.parse(url);
String filename = uri.getLastPathSegment();
DownloadManager downloadManager =
- (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
+ (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
downloadManager.enqueue(request);
@@ -534,17 +618,22 @@ public abstract class SFragment extends BaseFragment {
}
private void requestDownloadAllMedia(Status status) {
- String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
- ((BaseActivity) getActivity()).requestPermissions(permissions,
- (permissions1, grantResults) -> {
- if(grantResults.length > 0 &&
- grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- downloadAllMedia(status);
- } else {
- Toast.makeText(getContext(), R.string.error_media_download_permission,
- Toast.LENGTH_SHORT).show();
- }
- });
+ String[] permissions = new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE};
+ ((BaseActivity) getActivity())
+ .requestPermissions(
+ permissions,
+ (permissions1, grantResults) -> {
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ downloadAllMedia(status);
+ } else {
+ Toast.makeText(
+ getContext(),
+ R.string.error_media_download_permission,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ });
}
public boolean isFilteringMuted() {
@@ -554,39 +643,44 @@ public abstract class SFragment extends BaseFragment {
public void updateMuteFilter(@NonNull SharedPreferences pref, boolean reload) {
filterMuted = pref.getBoolean(PrefKeys.HIDE_MUTED_USERS, false);
- if(reload) {
+ if (reload) {
refreshAfterApplyingFilters();
}
}
public void reloadFilters(SharedPreferences pref, boolean forceRefresh) {
- if(pref != null) {
+ if (pref != null) {
updateMuteFilter(pref, false); // will be reloaded later
}
- if(filters != null && !forceRefresh) {
+ if (filters != null && !forceRefresh) {
applyFilters(forceRefresh);
return;
}
- mastodonApi.getValue().getFilters().enqueue(new Callback<List<Filter>>() {
- @Override
- public void onResponse(@NonNull Call<List<Filter>> call,
- @NonNull Response<List<Filter>> response)
- {
- filters = response.body();
- if(response.isSuccessful() && filters != null) {
- applyFilters(forceRefresh);
- } else {
- Log.e(TAG, "Error getting filters from server");
- }
- }
-
- @Override
- public void onFailure(@NonNull Call<List<Filter>> call, @NonNull Throwable t) {
- Log.e(TAG, "Error getting filters from server", t);
- }
- });
+ mastodonApi
+ .getValue()
+ .getFilters()
+ .enqueue(
+ new Callback<List<Filter>>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<List<Filter>> call,
+ @NonNull Response<List<Filter>> response) {
+ filters = response.body();
+ if (response.isSuccessful() && filters != null) {
+ applyFilters(forceRefresh);
+ } else {
+ Log.e(TAG, "Error getting filters from server");
+ }
+ }
+
+ @Override
+ public void onFailure(
+ @NonNull Call<List<Filter>> call, @NonNull Throwable t) {
+ Log.e(TAG, "Error getting filters from server", t);
+ }
+ });
}
protected boolean filterIsRelevant(@NonNull Filter filter) {
@@ -602,38 +696,40 @@ public abstract class SFragment extends BaseFragment {
@VisibleForTesting
public boolean shouldFilterStatus(Status status) {
- if(filterMuted && status.getMuted()) {
+ if (filterMuted && status.getMuted()) {
return true;
}
- if(filterRemoveRegex && status.getPoll() != null) {
- for(PollOption option : status.getPoll().getOptions()) {
- if(filterRemoveRegexMatcher.reset(option.getTitle()).find()) {
+ 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())));
+ return (filterRemoveRegex
+ && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find()
+ || (!status.getSpoilerText().isEmpty()
+ && filterRemoveRegexMatcher
+ .reset(status.getActionableStatus().getSpoilerText())
+ .find())));
}
public void applyFilters(boolean refresh) {
List<String> tokens = new ArrayList<>();
- for(Filter filter : filters) {
- if(filterIsRelevant(filter)) {
+ for (Filter filter : filters) {
+ if (filterIsRelevant(filter)) {
tokens.add(filterToRegexToken(filter));
}
}
filterRemoveRegex = !tokens.isEmpty();
- if(filterRemoveRegex) {
+ if (filterRemoveRegex) {
filterRemoveRegexMatcher =
- Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE).matcher("");
+ Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE)
+ .matcher("");
}
- if(refresh) {
+ if (refresh) {
refreshAfterApplyingFilters();
}
}
@@ -641,9 +737,11 @@ public abstract class SFragment extends BaseFragment {
private static String filterToRegexToken(Filter filter) {
String phrase = filter.getPhrase();
String quotedPhrase = Pattern.quote(phrase);
- return (filter.getWholeWord() && alphanumeric.reset(phrase).matches()) ?
- // "whole word" should only apply to alphanumeric filters, #1543
- String.format("(^|\\W)%s($|\\W)", quotedPhrase) : quotedPhrase;
+ return (filter.getWholeWord() && alphanumeric.reset(phrase).matches())
+ ?
+ // "whole word" should only apply to alphanumeric filters, #1543
+ String.format("(^|\\W)%s($|\\W)", quotedPhrase)
+ : quotedPhrase;
}
public static void flushFilters() {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java
index 1349a59..5663da2 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimePickerFragment.java
@@ -18,12 +18,9 @@ package com.keylesspalace.tusky.fragment;
import android.app.Dialog;
import android.app.TimePickerDialog;
import android.os.Bundle;
-
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
-
import com.keylesspalace.tusky.components.compose.ComposeActivity;
-
import java.util.Calendar;
import java.util.TimeZone;
@@ -42,12 +39,12 @@ public class TimePickerFragment extends DialogFragment {
calendar.set(Calendar.MINUTE, args.getInt(PICKER_TIME_MINUTE));
}
- return new TimePickerDialog(getContext(),
+ return new TimePickerDialog(
+ getContext(),
android.R.style.Theme_DeviceDefault_Dialog,
(ComposeActivity) getActivity(),
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
true);
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
index b2310e1..0f6a64d 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
@@ -20,6 +20,10 @@
package com.keylesspalace.tusky.fragment;
+import static com.uber.autodispose.AutoDispose.autoDisposable;
+import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -90,8 +94,6 @@ import com.keylesspalace.tusky.util.ViewDataUtils;
import com.keylesspalace.tusky.view.BackgroundMessageView;
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import static com.uber.autodispose.AutoDispose.autoDisposable;
-import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.io.IOException;
@@ -105,16 +107,16 @@ import java.util.concurrent.TimeUnit;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
-import static org.koin.java.KoinJavaComponent.inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;
public class TimelineFragment extends SFragment
- implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener, ReselectableFragment,
- RefreshableFragment
-{
+ implements SwipeRefreshLayout.OnRefreshListener,
+ StatusActionListener,
+ ReselectableFragment,
+ RefreshableFragment {
private static final String KIND_ARG = "kind";
private static final String ID_ARG = "id";
@@ -126,17 +128,28 @@ public class TimelineFragment extends SFragment
private boolean isNeedRefresh;
public enum Kind {
- HOME, PUBLIC_LOCAL, PUBLIC_FEDERATED, PUBLIC_BUBBLE, TAG, USER, USER_PINNED,
- USER_WITH_REPLIES, FAVOURITES, LIST, BOOKMARKS
+ HOME,
+ PUBLIC_LOCAL,
+ PUBLIC_FEDERATED,
+ PUBLIC_BUBBLE,
+ TAG,
+ USER,
+ USER_PINNED,
+ USER_WITH_REPLIES,
+ FAVOURITES,
+ LIST,
+ BOOKMARKS
}
private enum FetchEnd {
- TOP, BOTTOM, MIDDLE
+ TOP,
+ BOTTOM,
+ MIDDLE
}
private EventHub eventHub = (EventHub) inject(EventHub.class).getValue();
private TimelineRepository timelineRepo =
- (TimelineRepository) inject(TimelineRepository.class).getValue();
+ (TimelineRepository) inject(TimelineRepository.class).getValue();
private boolean eventRegistered = false;
@@ -150,10 +163,9 @@ public class TimelineFragment extends SFragment
private Kind kind;
private String id;
private List<String> tags;
- /**
- * For some timeline kinds we must use LINK headers and not just status ids.
- */
+ /** For some timeline kinds we must use LINK headers and not just status ids. */
private String nextId;
+
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private boolean filterRemoveReplies;
@@ -167,19 +179,20 @@ public class TimelineFragment extends SFragment
private boolean initialUpdateFailed = false;
private PairedList<Either<Placeholder, Status>, StatusViewData> statuses =
- new PairedList<>(new Function<>() {
- @Override
- public StatusViewData apply(Either<Placeholder, Status> input) {
- Status status = input.asRightOrNull();
- if(status != null) {
- return ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia,
- alwaysOpenSpoiler);
- } else {
- Placeholder placeholder = input.asLeft();
- return new StatusViewData.Placeholder(placeholder.getId(), false);
- }
- }
- });
+ new PairedList<>(
+ new Function<>() {
+ @Override
+ public StatusViewData apply(Either<Placeholder, Status> input) {
+ Status status = input.asRightOrNull();
+ if (status != null) {
+ return ViewDataUtils.statusToViewData(
+ status, alwaysShowSensitiveMedia, alwaysOpenSpoiler);
+ } else {
+ Placeholder placeholder = input.asLeft();
+ return new StatusViewData.Placeholder(placeholder.getId(), false);
+ }
+ }
+ });
public static TimelineFragment newInstance(Kind kind) {
return newInstance(kind, null);
@@ -189,9 +202,8 @@ public class TimelineFragment extends SFragment
return newInstance(kind, hashtagOrId, true);
}
- public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId,
- boolean enableSwipeToRefresh)
- {
+ public static TimelineFragment newInstance(
+ Kind kind, @Nullable String hashtagOrId, boolean enableSwipeToRefresh) {
TimelineFragment fragment = new TimelineFragment();
Bundle arguments = new Bundle(3);
arguments.putString(KIND_ARG, kind.name());
@@ -216,35 +228,39 @@ public class TimelineFragment extends SFragment
super.onCreate(savedInstanceState);
Bundle arguments = Objects.requireNonNull(getArguments());
kind = Kind.valueOf(arguments.getString(KIND_ARG));
- if(kind == Kind.USER || kind == Kind.USER_PINNED || kind == Kind.USER_WITH_REPLIES ||
- kind == Kind.LIST) {
+ if (kind == Kind.USER
+ || kind == Kind.USER_PINNED
+ || kind == Kind.USER_WITH_REPLIES
+ || kind == Kind.LIST) {
id = arguments.getString(ID_ARG);
}
- if(kind == Kind.TAG) {
+ if (kind == Kind.TAG) {
tags = arguments.getStringArrayList(HASHTAGS_ARG);
}
SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
+ PreferenceManager.getDefaultSharedPreferences(getActivity());
StatusDisplayOptions statusDisplayOptions =
- new StatusDisplayOptions(preferences.getBoolean("animateGifAvatars", false),
- accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
- preferences.getBoolean("absoluteTimeView", false),
- preferences.getBoolean("showBotOverlay", true),
- preferences.getBoolean("useBlurhash", true),
- preferences.getBoolean("showCardsInTimelines", false) ? CardViewMode.INDENTED :
- CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true),
- preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
- preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
+ new StatusDisplayOptions(
+ preferences.getBoolean("animateGifAvatars", false),
+ accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
+ preferences.getBoolean("absoluteTimeView", false),
+ preferences.getBoolean("showBotOverlay", true),
+ preferences.getBoolean("useBlurhash", true),
+ preferences.getBoolean("showCardsInTimelines", false)
+ ? CardViewMode.INDENTED
+ : CardViewMode.NONE,
+ preferences.getBoolean("confirmReblogs", true),
+ preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
+ preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this);
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true);
}
@Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
+ public View onCreateView(
+ @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
recyclerView = rootView.findViewById(R.id.recyclerView);
@@ -258,13 +274,13 @@ public class TimelineFragment extends SFragment
updateAdapter();
setupTimelinePreferences();
- if(statuses.isEmpty()) {
+ if (statuses.isEmpty()) {
progressBar.setVisibility(View.VISIBLE);
bottomLoading = true;
this.sendInitialRequest();
} else {
progressBar.setVisibility(View.GONE);
- if(isNeedRefresh) {
+ if (isNeedRefresh) {
onRefresh();
}
}
@@ -273,7 +289,7 @@ public class TimelineFragment extends SFragment
}
private void sendInitialRequest() {
- if(this.kind == Kind.HOME) {
+ if (this.kind == Kind.HOME) {
this.tryCache();
} else {
sendFetchTimelineRequest(null, null, null, FetchEnd.BOTTOM, -1);
@@ -283,84 +299,92 @@ public class TimelineFragment extends SFragment
private void tryCache() {
// Request timeline from disk to make it quick, then replace it with timeline from
// the server to update it
- timelineRepo.getStatuses(null, null, null, LOAD_AT_ONCE, TimelineRequestMode.DISK)
- .observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))).subscribe(statuses -> {
- filterStatuses(statuses);
-
- if(statuses.size() > 1) {
- this.clearPlaceholdersForResponse(statuses);
- this.statuses.clear();
- this.statuses.addAll(statuses);
- this.updateAdapter();
- this.progressBar.setVisibility(View.GONE);
- // Request statuses including current top to refresh all of them
- }
+ timelineRepo
+ .getStatuses(null, null, null, LOAD_AT_ONCE, TimelineRequestMode.DISK)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ statuses -> {
+ filterStatuses(statuses);
+
+ if (statuses.size() > 1) {
+ this.clearPlaceholdersForResponse(statuses);
+ this.statuses.clear();
+ this.statuses.addAll(statuses);
+ this.updateAdapter();
+ this.progressBar.setVisibility(View.GONE);
+ // Request statuses including current top to refresh all of them
+ }
- this.updateCurrent();
- this.loadAbove();
- }, throwable -> {
- this.updateCurrent();
- this.loadAbove();
- });
+ this.updateCurrent();
+ this.loadAbove();
+ },
+ throwable -> {
+ this.updateCurrent();
+ this.loadAbove();
+ });
}
private void updateCurrent() {
- if(this.statuses.isEmpty()) {
+ if (this.statuses.isEmpty()) {
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())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))).subscribe((statuses) -> {
- this.initialUpdateFailed = false;
- // When cached timeline is too old, we would replace it with nothing
- if(!statuses.isEmpty()) {
- filterStatuses(statuses);
-
- if(!this.statuses.isEmpty()) {
- // clear old cached statuses
- Iterator<Either<Placeholder, Status>> iterator = this.statuses.iterator();
- while(iterator.hasNext()) {
- Either<Placeholder, Status> item = iterator.next();
- if(item.isRight()) {
- Status status = item.asRight();
- if(status.getId().length() < topId.length() ||
- status.getId().compareTo(topId) < 0) {
-
- iterator.remove();
+ this.timelineRepo
+ .getStatuses(topId, null, null, LOAD_AT_ONCE, TimelineRequestMode.NETWORK)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (statuses) -> {
+ this.initialUpdateFailed = false;
+ // When cached timeline is too old, we would replace it with nothing
+ if (!statuses.isEmpty()) {
+ filterStatuses(statuses);
+
+ if (!this.statuses.isEmpty()) {
+ // clear old cached statuses
+ Iterator<Either<Placeholder, Status>> iterator =
+ this.statuses.iterator();
+ while (iterator.hasNext()) {
+ Either<Placeholder, Status> item = iterator.next();
+ if (item.isRight()) {
+ Status status = item.asRight();
+ if (status.getId().length() < topId.length()
+ || status.getId().compareTo(topId) < 0) {
+
+ iterator.remove();
+ }
+ } else {
+ Placeholder placeholder = item.asLeft();
+ if (placeholder.getId().length() < topId.length()
+ || placeholder.getId().compareTo(topId) < 0) {
+
+ iterator.remove();
+ }
+ }
+ }
}
- } else {
- Placeholder placeholder = item.asLeft();
- if(placeholder.getId().length() < topId.length() ||
- placeholder.getId().compareTo(topId) < 0) {
- iterator.remove();
- }
+ this.statuses.addAll(statuses);
+ this.updateAdapter();
}
-
- }
- }
-
- this.statuses.addAll(statuses);
- this.updateAdapter();
- }
- this.bottomLoading = false;
- this.progressBar.setVisibility(View.GONE);
- this.swipeRefreshLayout.setRefreshing(false);
- }, (e) -> {
- this.initialUpdateFailed = true;
- // Indicate that we are not loading anymore
- this.progressBar.setVisibility(View.GONE);
- this.swipeRefreshLayout.setRefreshing(false);
- });
+ this.bottomLoading = false;
+ this.progressBar.setVisibility(View.GONE);
+ this.swipeRefreshLayout.setRefreshing(false);
+ },
+ (e) -> {
+ this.initialUpdateFailed = true;
+ // Indicate that we are not loading anymore
+ this.progressBar.setVisibility(View.GONE);
+ this.swipeRefreshLayout.setRefreshing(false);
+ });
}
private void setupTimelinePreferences() {
alwaysShowSensitiveMedia =
- accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
+ accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getValue().getActiveAccount().getAlwaysOpenSpoiler();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
@@ -375,7 +399,7 @@ public class TimelineFragment extends SFragment
private static boolean filterContextMatchesKind(Kind kind, List<String> filterContext) {
// home, notifications, public, thread
- switch(kind) {
+ switch (kind) {
case HOME:
case LIST:
return filterContext.contains(Filter.HOME);
@@ -385,8 +409,8 @@ public class TimelineFragment extends SFragment
case TAG:
return filterContext.contains(Filter.PUBLIC);
case FAVOURITES:
- return (filterContext.contains(Filter.PUBLIC) ||
- filterContext.contains(Filter.NOTIFICATIONS));
+ return (filterContext.contains(Filter.PUBLIC)
+ || filterContext.contains(Filter.NOTIFICATIONS));
case USER:
case USER_WITH_REPLIES:
case USER_PINNED:
@@ -408,7 +432,7 @@ public class TimelineFragment extends SFragment
private void setupSwipeRefreshLayout() {
swipeRefreshLayout.setEnabled(isSwipeToRefreshEnabled);
- if(isSwipeToRefreshEnabled) {
+ if (isSwipeToRefreshEnabled) {
swipeRefreshLayout.setOnRefreshListener(this);
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue);
}
@@ -416,13 +440,14 @@ public class TimelineFragment extends SFragment
private void setupRecyclerView() {
recyclerView.setAccessibilityDelegateCompat(
- new ListStatusAccessibilityDelegate(recyclerView, this, statuses::getPairedItemOrNull));
+ new ListStatusAccessibilityDelegate(
+ recyclerView, this, statuses::getPairedItemOrNull));
Context context = recyclerView.getContext();
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
DividerItemDecoration divider =
- new DividerItemDecoration(context, layoutManager.getOrientation());
+ new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(divider);
// CWs are expanded without animation, buttons animate itself, we don't need it basically
@@ -432,15 +457,15 @@ public class TimelineFragment extends SFragment
}
private void deleteStatusById(String id) {
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
Either<Placeholder, Status> either = statuses.get(i);
- if(either.isRight() && id.equals(either.asRight().getId())) {
+ if (either.isRight() && id.equals(either.asRight().getId())) {
statuses.remove(either);
updateAdapter();
break;
}
}
- if(statuses.size() == 0) {
+ if (statuses.size() == 0) {
showNothing();
}
}
@@ -455,139 +480,150 @@ public class TimelineFragment extends SFragment
super.onActivityCreated(savedInstanceState);
SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(requireContext());
+ PreferenceManager.getDefaultSharedPreferences(requireContext());
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
* guaranteed to be set until then. */
- if(actionButtonPresent()) {
+ if (actionButtonPresent()) {
/* Use a modified scroll listener that both loads more statuses as it goes, and hides
* the follow button on down-scroll. */
hideFab = preferences.getBoolean("fabHide", false);
- scrollListener = new EndlessOnScrollListener(layoutManager) {
- @Override
- public void onScrolled(RecyclerView view, int dx, int dy) {
- super.onScrolled(view, dx, dy);
-
- ActionButtonActivity activity = (ActionButtonActivity) getActivity();
- FloatingActionButton composeButton = activity.getActionButton();
-
- if(composeButton != null) {
- if(hideFab) {
- if(dy > 0 && composeButton.isShown()) {
- composeButton.hide(); // hides the button if we're scrolling down
- activity.onActionButtonHidden();
- } else if(dy < 0 && !composeButton.isShown()) {
- composeButton.show(); // shows it if we are scrolling up
+ scrollListener =
+ new EndlessOnScrollListener(layoutManager) {
+ @Override
+ public void onScrolled(RecyclerView view, int dx, int dy) {
+ super.onScrolled(view, dx, dy);
+
+ ActionButtonActivity activity = (ActionButtonActivity) getActivity();
+ FloatingActionButton composeButton = activity.getActionButton();
+
+ if (composeButton != null) {
+ if (hideFab) {
+ if (dy > 0 && composeButton.isShown()) {
+ composeButton
+ .hide(); // hides the button if we're scrolling down
+ activity.onActionButtonHidden();
+ } else if (dy < 0 && !composeButton.isShown()) {
+ composeButton.show(); // shows it if we are scrolling up
+ }
+ } else if (!composeButton.isShown()) {
+ composeButton.show();
+ }
}
- } else if(!composeButton.isShown()) {
- composeButton.show();
}
- }
- }
- @Override
- public void onLoadMore(int totalItemsCount, RecyclerView view) {
- TimelineFragment.this.onLoadMore();
- }
- };
+ @Override
+ public void onLoadMore(int totalItemsCount, RecyclerView view) {
+ TimelineFragment.this.onLoadMore();
+ }
+ };
} else {
// Just use the basic scroll listener to load more statuses.
- scrollListener = new EndlessOnScrollListener(layoutManager) {
- @Override
- public void onLoadMore(int totalItemsCount, RecyclerView view) {
- TimelineFragment.this.onLoadMore();
- }
- };
+ scrollListener =
+ new EndlessOnScrollListener(layoutManager) {
+ @Override
+ public void onLoadMore(int totalItemsCount, RecyclerView view) {
+ TimelineFragment.this.onLoadMore();
+ }
+ };
}
recyclerView.addOnScrollListener(scrollListener);
- if(!eventRegistered) {
- eventHub.getEvents().observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))).subscribe(event -> {
- if(event instanceof FavoriteEvent) {
- FavoriteEvent favEvent = ((FavoriteEvent) event);
- handleFavEvent(favEvent);
- } else if(event instanceof ReblogEvent) {
- ReblogEvent reblogEvent = (ReblogEvent) event;
- handleReblogEvent(reblogEvent);
- } else if(event instanceof BookmarkEvent) {
- BookmarkEvent bookmarkEvent = (BookmarkEvent) event;
- handleBookmarkEvent(bookmarkEvent);
- } else if(event instanceof UnfollowEvent) {
- if(kind == Kind.HOME) {
- String id = ((UnfollowEvent) event).getAccountId();
- removeAllByAccountId(id);
- }
- } else if(event instanceof BlockEvent) {
- if(kind != Kind.USER && kind != Kind.USER_WITH_REPLIES &&
- kind != Kind.USER_PINNED) {
- String id = ((BlockEvent) event).getAccountId();
- removeAllByAccountId(id);
- }
- } else if(event instanceof MuteConversationEvent) {
- if(kind != Kind.USER && kind != Kind.USER_WITH_REPLIES &&
- kind != Kind.USER_PINNED) {
- handleMuteStatusEvent((MuteConversationEvent) event);
- }
- } else if(event instanceof MuteEvent) {
- if(kind != Kind.USER && kind != Kind.USER_WITH_REPLIES &&
- kind != Kind.USER_PINNED) {
- handleMuteEvent((MuteEvent) event);
- }
- } else if(event instanceof DomainMuteEvent) {
- if(kind != Kind.USER && kind != Kind.USER_WITH_REPLIES &&
- kind != Kind.USER_PINNED) {
- String instance = ((DomainMuteEvent) event).getInstance();
- removeAllByInstance(instance);
- }
- } else if(event instanceof StatusDeletedEvent) {
- if(kind != Kind.USER && kind != Kind.USER_WITH_REPLIES &&
- kind != Kind.USER_PINNED) {
- String id = ((StatusDeletedEvent) event).getStatusId();
- deleteStatusById(id);
- }
- } else if(event instanceof StatusComposedEvent) {
- Status status = ((StatusComposedEvent) event).getStatus();
- handleStatusComposeEvent(status);
- } else if(event instanceof PreferenceChangedEvent) {
- onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
- } else if(event instanceof EmojiReactEvent) {
- handleEmojiReactEvent((EmojiReactEvent) event);
- }
- });
+ if (!eventRegistered) {
+ eventHub.getEvents()
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ event -> {
+ if (event instanceof FavoriteEvent) {
+ FavoriteEvent favEvent = ((FavoriteEvent) event);
+ handleFavEvent(favEvent);
+ } else if (event instanceof ReblogEvent) {
+ ReblogEvent reblogEvent = (ReblogEvent) event;
+ handleReblogEvent(reblogEvent);
+ } else if (event instanceof BookmarkEvent) {
+ BookmarkEvent bookmarkEvent = (BookmarkEvent) event;
+ handleBookmarkEvent(bookmarkEvent);
+ } else if (event instanceof UnfollowEvent) {
+ if (kind == Kind.HOME) {
+ String id = ((UnfollowEvent) event).getAccountId();
+ removeAllByAccountId(id);
+ }
+ } else if (event instanceof BlockEvent) {
+ if (kind != Kind.USER
+ && kind != Kind.USER_WITH_REPLIES
+ && kind != Kind.USER_PINNED) {
+ String id = ((BlockEvent) event).getAccountId();
+ removeAllByAccountId(id);
+ }
+ } else if (event instanceof MuteConversationEvent) {
+ if (kind != Kind.USER
+ && kind != Kind.USER_WITH_REPLIES
+ && kind != Kind.USER_PINNED) {
+ handleMuteStatusEvent((MuteConversationEvent) event);
+ }
+ } else if (event instanceof MuteEvent) {
+ if (kind != Kind.USER
+ && kind != Kind.USER_WITH_REPLIES
+ && kind != Kind.USER_PINNED) {
+ handleMuteEvent((MuteEvent) event);
+ }
+ } else if (event instanceof DomainMuteEvent) {
+ if (kind != Kind.USER
+ && kind != Kind.USER_WITH_REPLIES
+ && kind != Kind.USER_PINNED) {
+ String instance = ((DomainMuteEvent) event).getInstance();
+ removeAllByInstance(instance);
+ }
+ } else if (event instanceof StatusDeletedEvent) {
+ if (kind != Kind.USER
+ && kind != Kind.USER_WITH_REPLIES
+ && kind != Kind.USER_PINNED) {
+ String id = ((StatusDeletedEvent) event).getStatusId();
+ deleteStatusById(id);
+ }
+ } else if (event instanceof StatusComposedEvent) {
+ Status status = ((StatusComposedEvent) event).getStatus();
+ handleStatusComposeEvent(status);
+ } else if (event instanceof PreferenceChangedEvent) {
+ onPreferenceChanged(
+ ((PreferenceChangedEvent) event).getPreferenceKey());
+ } else if (event instanceof EmojiReactEvent) {
+ handleEmojiReactEvent((EmojiReactEvent) event);
+ }
+ });
eventRegistered = true;
}
}
@Override
public void onRefresh() {
- if(isSwipeToRefreshEnabled) {
+ if (isSwipeToRefreshEnabled) {
swipeRefreshLayout.setEnabled(true);
}
this.statusView.setVisibility(View.GONE);
isNeedRefresh = false;
- if(this.initialUpdateFailed) {
+ if (this.initialUpdateFailed) {
updateCurrent();
}
this.loadAbove();
-
}
private void loadAbove() {
String firstOrNull = null;
String secondOrNull = null;
- for(int i = 0; i < this.statuses.size(); i++) {
+ for (int i = 0; i < this.statuses.size(); i++) {
Either<Placeholder, Status> status = this.statuses.get(i);
- if(status.isRight()) {
+ if (status.isRight()) {
firstOrNull = status.asRight().getId();
- if(i + 1 < statuses.size() && statuses.get(i + 1).isRight()) {
+ if (i + 1 < statuses.size() && statuses.get(i + 1).isRight()) {
secondOrNull = statuses.get(i + 1).asRight().getId();
}
break;
}
}
- if(firstOrNull != null) {
+ if (firstOrNull != null) {
this.sendFetchTimelineRequest(null, firstOrNull, secondOrNull, FetchEnd.TOP, -1);
} else {
this.sendFetchTimelineRequest(null, null, null, FetchEnd.BOTTOM, -1);
@@ -602,27 +638,38 @@ public class TimelineFragment extends SFragment
@Override
public void onReblog(final boolean reblog, final int position) {
final Status status = statuses.get(position).asRight();
- timelineCases.getValue().reblog(status, reblog).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((newStatus) -> setRebloggedForStatus(position, status, reblog),
- (err) -> Timber.e(
- "Failed to reblog status " + status.getId() + ", Error[" + err + "]"));
+ timelineCases
+ .getValue()
+ .reblog(status, reblog)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (newStatus) -> setRebloggedForStatus(position, status, reblog),
+ (err) ->
+ Timber.e(
+ "Failed to reblog status "
+ + status.getId()
+ + ", Error["
+ + err
+ + "]"));
}
private void setRebloggedForStatus(int position, Status status, boolean reblog) {
status.setReblogged(reblog);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setReblogged(reblog);
}
Pair<StatusViewData.Concrete, Integer> actual = findStatusAndPosition(position, status);
- if(actual == null) {
+ if (actual == null) {
return;
}
StatusViewData newViewData =
- new StatusViewData.Builder(actual.first).setReblogged(reblog).createStatusViewData();
+ new StatusViewData.Builder(actual.first)
+ .setReblogged(reblog)
+ .createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
@@ -630,29 +677,38 @@ public class TimelineFragment extends SFragment
@Override
public void onFavourite(final boolean favourite, final int position) {
final Status status = statuses.get(position).asRight();
- timelineCases.getValue().favourite(status, favourite)
- .observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((newStatus) -> setFavouriteForStatus(position, newStatus, favourite),
- (err) -> Timber.e(
- "Failed to favourite status " + status.getId() + ", Error [" + err + "]"));
+ timelineCases
+ .getValue()
+ .favourite(status, favourite)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (newStatus) -> setFavouriteForStatus(position, newStatus, favourite),
+ (err) ->
+ Timber.e(
+ "Failed to favourite status "
+ + status.getId()
+ + ", Error ["
+ + err
+ + "]"));
}
private void setFavouriteForStatus(int position, Status status, boolean favourite) {
status.setFavourited(favourite);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setFavourited(favourite);
}
Pair<StatusViewData.Concrete, Integer> actual = findStatusAndPosition(position, status);
- if(actual == null) {
+ if (actual == null) {
return;
}
StatusViewData newViewData =
- new StatusViewData.Builder(actual.first).setFavourited(favourite)
- .createStatusViewData();
+ new StatusViewData.Builder(actual.first)
+ .setFavourited(favourite)
+ .createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
@@ -660,47 +716,54 @@ public class TimelineFragment extends SFragment
@Override
public void onBookmark(final boolean bookmark, final int position) {
final Status status = statuses.get(position).asRight();
- timelineCases.getValue().bookmark(status, bookmark)
- .observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((newStatus) -> setBookmarkForStatus(position, newStatus, bookmark),
- (err) -> Timber.e(err, "Failed to favourite status " + status.getId()));
+ timelineCases
+ .getValue()
+ .bookmark(status, bookmark)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (newStatus) -> setBookmarkForStatus(position, newStatus, bookmark),
+ (err) -> Timber.e(err, "Failed to favourite status " + status.getId()));
}
private void setBookmarkForStatus(int position, Status status, boolean bookmark) {
status.setBookmarked(bookmark);
- if(status.getReblog() != null) {
+ if (status.getReblog() != null) {
status.getReblog().setBookmarked(bookmark);
}
Pair<StatusViewData.Concrete, Integer> actual = findStatusAndPosition(position, status);
- if(actual == null) {
+ if (actual == null) {
return;
}
StatusViewData newViewData =
- new StatusViewData.Builder(actual.first).setBookmarked(bookmark).createStatusViewData();
+ new StatusViewData.Builder(actual.first)
+ .setBookmarked(bookmark)
+ .createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
@Override
public void onMute(int position, boolean isMuted) {
- StatusViewData.Concrete statusViewData = new StatusViewData.Builder(
- (StatusViewData.Concrete) statuses.getPairedItem(position)).setMuted(isMuted)
- .createStatusViewData();
+ StatusViewData.Concrete statusViewData =
+ new StatusViewData.Builder(
+ (StatusViewData.Concrete) statuses.getPairedItem(position))
+ .setMuted(isMuted)
+ .createStatusViewData();
statuses.setPairedItem(position, statusViewData);
updateAdapter();
}
- private void setMutedStatusForStatus(int position, Status status, boolean muted,
- boolean threadMuted)
- {
+ private void setMutedStatusForStatus(
+ int position, Status status, boolean muted, boolean threadMuted) {
status.setThreadMuted(threadMuted);
StatusViewData.Builder statusViewData =
- new StatusViewData.Builder((StatusViewData.Concrete) statuses.getPairedItem(position));
+ new StatusViewData.Builder(
+ (StatusViewData.Concrete) statuses.getPairedItem(position));
statusViewData.setMuted(muted);
statusViewData.setThreadMuted(threadMuted);
@@ -715,20 +778,24 @@ public class TimelineFragment extends SFragment
setVoteForPoll(position, status, votedPoll);
- timelineCases.getValue().voteInPoll(status, choices)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newPoll) -> setVoteForPoll(position, status, newPoll),
- (t) -> Timber.e(t, "Failed to vote in poll: " + status.getId()));
+ timelineCases
+ .getValue()
+ .voteInPoll(status, choices)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newPoll) -> setVoteForPoll(position, status, newPoll),
+ (t) -> Timber.e(t, "Failed to vote in poll: " + status.getId()));
}
private void setVoteForPoll(int position, Status status, Poll newPoll) {
Pair<StatusViewData.Concrete, Integer> actual = findStatusAndPosition(position, status);
- if(actual == null) {
+ if (actual == null) {
return;
}
StatusViewData newViewData =
- new StatusViewData.Builder(actual.first).setPoll(newPoll).createStatusViewData();
+ new StatusViewData.Builder(actual.first).setPoll(newPoll).createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
@@ -745,29 +812,32 @@ public class TimelineFragment extends SFragment
@Override
public void onExpandedChange(boolean expanded, int position) {
- StatusViewData newViewData = new StatusViewData.Builder(
- ((StatusViewData.Concrete) statuses.getPairedItem(position))).setIsExpanded(expanded)
- .createStatusViewData();
+ StatusViewData newViewData =
+ new StatusViewData.Builder(
+ ((StatusViewData.Concrete) statuses.getPairedItem(position)))
+ .setIsExpanded(expanded)
+ .createStatusViewData();
statuses.setPairedItem(position, newViewData);
updateAdapter();
}
@Override
public void onContentHiddenChange(boolean isShowing, int position) {
- StatusViewData newViewData = new StatusViewData.Builder(
- ((StatusViewData.Concrete) statuses.getPairedItem(
- position))).setIsShowingSensitiveContent(isShowing).createStatusViewData();
+ StatusViewData newViewData =
+ new StatusViewData.Builder(
+ ((StatusViewData.Concrete) statuses.getPairedItem(position)))
+ .setIsShowingSensitiveContent(isShowing)
+ .createStatusViewData();
statuses.setPairedItem(position, newViewData);
updateAdapter();
}
-
@Override
public void onShowReblogs(int position) {
String statusId = statuses.get(position).asRight().getId();
Intent intent =
- AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REBLOGGED,
- statusId);
+ AccountListActivity.newIntent(
+ getContext(), AccountListActivity.Type.REBLOGGED, statusId);
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
}
@@ -775,26 +845,27 @@ public class TimelineFragment extends SFragment
public void onShowFavs(int position) {
String statusId = statuses.get(position).asRight().getId();
Intent intent =
- AccountListActivity.newIntent(getContext(), AccountListActivity.Type.FAVOURITED,
- statusId);
+ AccountListActivity.newIntent(
+ getContext(), AccountListActivity.Type.FAVOURITED, statusId);
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
}
@Override
public void onLoadMore(int position) {
- //check bounds before accessing list,
- if(statuses.size() >= position && position > 0) {
+ // check bounds before accessing list,
+ if (statuses.size() >= position && position > 0) {
Status fromStatus = statuses.get(position - 1).asRightOrNull();
Status toStatus = statuses.get(position + 1).asRightOrNull();
String maxMinusOne =
- statuses.size() > position + 1 && statuses.get(position + 2).isRight() ?
- statuses.get(position + 1).asRight().getId() : null;
- if(fromStatus == null || toStatus == null) {
+ statuses.size() > position + 1 && statuses.get(position + 2).isRight()
+ ? statuses.get(position + 1).asRight().getId()
+ : null;
+ if (fromStatus == null || toStatus == null) {
Timber.e("Failed to load more at " + position + ", wrong placeholder position");
return;
}
- sendFetchTimelineRequest(fromStatus.getId(), toStatus.getId(), maxMinusOne,
- FetchEnd.MIDDLE, position);
+ sendFetchTimelineRequest(
+ fromStatus.getId(), toStatus.getId(), maxMinusOne, FetchEnd.MIDDLE, position);
Placeholder placeholder = statuses.get(position).asLeft();
StatusViewData newViewData = new StatusViewData.Placeholder(placeholder.getId(), true);
@@ -807,27 +878,33 @@ public class TimelineFragment extends SFragment
@Override
public void onContentCollapsedChange(boolean isCollapsed, int position) {
- if(position < 0 || position >= statuses.size()) {
+ if (position < 0 || position >= statuses.size()) {
Timber.e(
- String.format("Tried to access out of bounds status position: %d of %d", position,
- statuses.size() - 1));
+ String.format(
+ "Tried to access out of bounds status position: %d of %d",
+ position, statuses.size() - 1));
return;
}
StatusViewData status = statuses.getPairedItem(position);
- if(!(status instanceof StatusViewData.Concrete)) {
+ if (!(status instanceof StatusViewData.Concrete)) {
// Statuses PairedList contains a base type of StatusViewData.Concrete and also doesn't
- // check for null values when adding values to it although this doesn't seem to be an issue.
- Timber.e(String.format(
- "Expected StatusViewData.Concrete, got %s instead at position: %d of %d",
- status == null ? "<null>" : status.getClass().getSimpleName(), position,
- statuses.size() - 1));
+ // check for null values when adding values to it although this doesn't seem to be an
+ // issue.
+ Timber.e(
+ String.format(
+ "Expected StatusViewData.Concrete, got %s instead at position: %d of"
+ + " %d",
+ status == null ? "<null>" : status.getClass().getSimpleName(),
+ position,
+ statuses.size() - 1));
return;
}
StatusViewData updatedStatus =
- new StatusViewData.Builder((StatusViewData.Concrete) status).setCollapsed(isCollapsed)
- .createStatusViewData();
+ new StatusViewData.Builder((StatusViewData.Concrete) status)
+ .setCollapsed(isCollapsed)
+ .createStatusViewData();
statuses.setPairedItem(position, updatedStatus);
updateAdapter();
}
@@ -835,7 +912,7 @@ public class TimelineFragment extends SFragment
@Override
public void onViewMedia(int position, int attachmentIndex, @Nullable View view) {
Status status = statuses.get(position).asRightOrNull();
- if(status == null) {
+ if (status == null) {
return;
}
super.viewMedia(attachmentIndex, status, view);
@@ -849,13 +926,15 @@ public class TimelineFragment extends SFragment
@Override
public void onViewReplyTo(int position) {
Status status = statuses.get(position).asRightOrNull();
- if(status == null) {
+ if (status == null) {
return;
}
- String replyToId = status.getReblog() == null ? status.getInReplyToId() :
- status.getReblog().getInReplyToId();
- if(replyToId == null) {
+ String replyToId =
+ status.getReblog() == null
+ ? status.getInReplyToId()
+ : status.getReblog().getInReplyToId();
+ if (replyToId == null) {
return;
}
super.onShowReplyTo(replyToId);
@@ -863,7 +942,7 @@ public class TimelineFragment extends SFragment
@Override
public void onViewTag(String tag) {
- if(kind == Kind.TAG && tags.size() == 1 && tags.contains(tag)) {
+ if (kind == Kind.TAG && tags.size() == 1 && tags.contains(tag)) {
// If already viewing a tag page, then ignore any request to view that tag again.
return;
}
@@ -872,7 +951,7 @@ public class TimelineFragment extends SFragment
@Override
public void onViewAccount(String id) {
- if((kind == Kind.USER || kind == Kind.USER_WITH_REPLIES) && this.id.equals(id)) {
+ if ((kind == Kind.USER || kind == Kind.USER_WITH_REPLIES) && this.id.equals(id)) {
/* If already viewing an account page, then any requests to view that account page
* should be ignored. */
return;
@@ -882,60 +961,70 @@ public class TimelineFragment extends SFragment
private void onPreferenceChanged(String key) {
SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(getContext());
- switch(key) {
- case "fabHide": {
- hideFab = sharedPreferences.getBoolean("fabHide", false);
- break;
- }
- case "mediaPreviewEnabled": {
- boolean enabled =
- accountManager.getValue().getActiveAccount().getMediaPreviewEnabled();
- boolean oldMediaPreviewEnabled = adapter.getMediaPreviewEnabled();
- if(enabled != oldMediaPreviewEnabled) {
- adapter.setMediaPreviewEnabled(enabled);
- fullyRefresh();
+ PreferenceManager.getDefaultSharedPreferences(getContext());
+ switch (key) {
+ case "fabHide":
+ {
+ hideFab = sharedPreferences.getBoolean("fabHide", false);
+ break;
}
- break;
- }
- case "tabFilterHomeReplies": {
- boolean filter = sharedPreferences.getBoolean("tabFilterHomeReplies", true);
- boolean oldRemoveReplies = filterRemoveReplies;
- filterRemoveReplies = kind == Kind.HOME && !filter;
- if(adapter.getItemCount() > 1 && oldRemoveReplies != filterRemoveReplies) {
- fullyRefresh();
+ case "mediaPreviewEnabled":
+ {
+ boolean enabled =
+ accountManager.getValue().getActiveAccount().getMediaPreviewEnabled();
+ boolean oldMediaPreviewEnabled = adapter.getMediaPreviewEnabled();
+ if (enabled != oldMediaPreviewEnabled) {
+ adapter.setMediaPreviewEnabled(enabled);
+ fullyRefresh();
+ }
+ break;
}
- break;
- }
- case "tabFilterHomeBoosts": {
- boolean filter = sharedPreferences.getBoolean("tabFilterHomeBoosts", true);
- boolean oldRemoveReblogs = filterRemoveReblogs;
- filterRemoveReblogs = kind == Kind.HOME && !filter;
- if(adapter.getItemCount() > 1 && oldRemoveReblogs != filterRemoveReblogs) {
- fullyRefresh();
+ case "tabFilterHomeReplies":
+ {
+ boolean filter = sharedPreferences.getBoolean("tabFilterHomeReplies", true);
+ boolean oldRemoveReplies = filterRemoveReplies;
+ filterRemoveReplies = kind == Kind.HOME && !filter;
+ if (adapter.getItemCount() > 1 && oldRemoveReplies != filterRemoveReplies) {
+ fullyRefresh();
+ }
+ break;
+ }
+ case "tabFilterHomeBoosts":
+ {
+ boolean filter = sharedPreferences.getBoolean("tabFilterHomeBoosts", true);
+ boolean oldRemoveReblogs = filterRemoveReblogs;
+ filterRemoveReblogs = kind == Kind.HOME && !filter;
+ if (adapter.getItemCount() > 1 && oldRemoveReblogs != filterRemoveReblogs) {
+ fullyRefresh();
+ }
+ break;
+ }
+ case PrefKeys.HIDE_MUTED_USERS:
+ {
+ updateMuteFilter(sharedPreferences, true);
+ break;
}
- break;
- }
- case PrefKeys.HIDE_MUTED_USERS: {
- updateMuteFilter(sharedPreferences, true);
- break;
- }
case Filter.HOME:
case Filter.NOTIFICATIONS:
case Filter.THREAD:
case Filter.PUBLIC:
- case Filter.ACCOUNT: {
- if(filterContextMatchesKind(kind, Collections.singletonList(key))) {
- reloadFilters(sharedPreferences, true);
+ case Filter.ACCOUNT:
+ {
+ if (filterContextMatchesKind(kind, Collections.singletonList(key))) {
+ reloadFilters(sharedPreferences, true);
+ }
+ break;
+ }
+ case "alwaysShowSensitiveMedia":
+ {
+ // it is ok if only newly loaded statuses are affected, no need to fully refresh
+ alwaysShowSensitiveMedia =
+ accountManager
+ .getValue()
+ .getActiveAccount()
+ .getAlwaysShowSensitiveMedia();
+ break;
}
- break;
- }
- case "alwaysShowSensitiveMedia": {
- //it is ok if only newly loaded statuses are affected, no need to fully refresh
- alwaysShowSensitiveMedia =
- accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
- break;
- }
}
}
@@ -948,10 +1037,12 @@ public class TimelineFragment extends SFragment
private void removeAllByConversationId(String conversationId) {
// using iterator to safely remove items while iterating
Iterator<Either<Placeholder, Status>> iterator = statuses.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Status status = iterator.next().asRightOrNull();
- if(status != null && (status.getConversationId().equalsIgnoreCase(conversationId)) ||
- status.getActionableStatus().getConversationId().equalsIgnoreCase(conversationId)) {
+ if (status != null && (status.getConversationId().equalsIgnoreCase(conversationId))
+ || status.getActionableStatus()
+ .getConversationId()
+ .equalsIgnoreCase(conversationId)) {
iterator.remove();
}
}
@@ -961,11 +1052,14 @@ public class TimelineFragment extends SFragment
private void removeAllByAccountId(String accountId) {
// using iterator to safely remove items while iterating
Iterator<Either<Placeholder, Status>> iterator = statuses.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Status status = iterator.next().asRightOrNull();
- if(status != null && (status.getAccount().getId().equals(accountId) ||
- status.getActionableStatus().getAccount().getId()
- .equals(accountId))) {
+ if (status != null
+ && (status.getAccount().getId().equals(accountId)
+ || status.getActionableStatus()
+ .getAccount()
+ .getId()
+ .equals(accountId))) {
iterator.remove();
}
}
@@ -975,10 +1069,10 @@ public class TimelineFragment extends SFragment
private void removeAllByInstance(String instance) {
// using iterator to safely remove items while iterating
Iterator<Either<Placeholder, Status>> iterator = statuses.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Status status = iterator.next().asRightOrNull();
- if(status != null &&
- LinkHelper.getDomain(status.getAccount().getUrl()).equals(instance)) {
+ if (status != null
+ && LinkHelper.getDomain(status.getAccount().getUrl()).equals(instance)) {
iterator.remove();
}
}
@@ -986,11 +1080,11 @@ public class TimelineFragment extends SFragment
}
private void onLoadMore() {
- if(didLoadEverythingBottom || bottomLoading) {
+ if (didLoadEverythingBottom || bottomLoading) {
return;
}
- if(statuses.size() == 0) {
+ if (statuses.size() == 0) {
sendInitialRequest();
return;
}
@@ -999,27 +1093,27 @@ public class TimelineFragment extends SFragment
Either<Placeholder, Status> last = statuses.get(statuses.size() - 1);
Placeholder placeholder;
- if(last.isRight()) {
+ if (last.isRight()) {
final String placeholderId = StringUtils.dec(last.asRight().getId());
placeholder = new Placeholder(placeholderId);
statuses.add(new Either.Left<>(placeholder));
} else {
placeholder = last.asLeft();
}
- statuses.setPairedItem(statuses.size() - 1,
- new StatusViewData.Placeholder(placeholder.getId(), true));
+ statuses.setPairedItem(
+ statuses.size() - 1, new StatusViewData.Placeholder(placeholder.getId(), true));
updateAdapter();
String bottomId = null;
- if(kind == Kind.FAVOURITES || kind == Kind.BOOKMARKS) {
+ if (kind == Kind.FAVOURITES || kind == Kind.BOOKMARKS) {
bottomId = this.nextId;
} else {
final ListIterator<Either<Placeholder, Status>> iterator =
- this.statuses.listIterator(this.statuses.size());
- while(iterator.hasPrevious()) {
+ this.statuses.listIterator(this.statuses.size());
+ while (iterator.hasPrevious()) {
Either<Placeholder, Status> previous = iterator.previous();
- if(previous.isRight()) {
+ if (previous.isRight()) {
bottomId = previous.asRight().getId();
break;
}
@@ -1036,12 +1130,14 @@ public class TimelineFragment extends SFragment
}
private boolean actionButtonPresent() {
- return kind != Kind.TAG && kind != Kind.FAVOURITES && kind != Kind.BOOKMARKS &&
- getActivity() instanceof ActionButtonActivity;
+ return kind != Kind.TAG
+ && kind != Kind.FAVOURITES
+ && kind != Kind.BOOKMARKS
+ && getActivity() instanceof ActionButtonActivity;
}
private void jumpToTop() {
- if(isAdded()) {
+ if (isAdded()) {
layoutManager.scrollToPosition(0);
recyclerView.stopScroll();
scrollListener.reset();
@@ -1049,7 +1145,7 @@ public class TimelineFragment extends SFragment
}
private Call<List<Status>> getFetchCallByTimelineType(String fromId, String uptoId) {
- switch(kind) {
+ switch (kind) {
default:
case HOME:
return mastodonApi.getValue().homeTimeline(fromId, uptoId, LOAD_AT_ONCE);
@@ -1062,18 +1158,27 @@ public class TimelineFragment extends SFragment
case TAG:
String firstHashtag = tags.get(0);
List<String> additionalHashtags = tags.subList(1, tags.size());
- return mastodonApi.getValue()
- .hashtagTimeline(firstHashtag, additionalHashtags, null, fromId, uptoId,
- LOAD_AT_ONCE);
+ return mastodonApi
+ .getValue()
+ .hashtagTimeline(
+ firstHashtag,
+ additionalHashtags,
+ null,
+ fromId,
+ uptoId,
+ LOAD_AT_ONCE);
case USER:
- return mastodonApi.getValue()
- .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, true, null, null);
+ return mastodonApi
+ .getValue()
+ .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, true, null, null);
case USER_PINNED:
- return mastodonApi.getValue()
- .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, true);
+ return mastodonApi
+ .getValue()
+ .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, true);
case USER_WITH_REPLIES:
- return mastodonApi.getValue()
- .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, null);
+ return mastodonApi
+ .getValue()
+ .accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, null);
case FAVOURITES:
return mastodonApi.getValue().favourites(fromId, uptoId, LOAD_AT_ONCE);
case BOOKMARKS:
@@ -1083,53 +1188,67 @@ public class TimelineFragment extends SFragment
}
}
- private void sendFetchTimelineRequest(@Nullable String maxId, @Nullable String sinceId,
- @Nullable String sinceIdMinusOne, final FetchEnd fetchEnd, final int pos)
- {
- if(isAdded() && (fetchEnd == FetchEnd.TOP || fetchEnd == FetchEnd.BOTTOM && maxId == null &&
- progressBar.getVisibility() != View.VISIBLE) &&
- !isSwipeToRefreshEnabled) {
+ private void sendFetchTimelineRequest(
+ @Nullable String maxId,
+ @Nullable String sinceId,
+ @Nullable String sinceIdMinusOne,
+ final FetchEnd fetchEnd,
+ final int pos) {
+ if (isAdded()
+ && (fetchEnd == FetchEnd.TOP
+ || fetchEnd == FetchEnd.BOTTOM
+ && maxId == null
+ && progressBar.getVisibility() != View.VISIBLE)
+ && !isSwipeToRefreshEnabled) {
topProgressBar.show();
}
- if(kind == Kind.HOME) {
+ if (kind == Kind.HOME) {
TimelineRequestMode mode;
// allow getting old statuses/fallbacks for network only for for bottom loading
- if(fetchEnd == FetchEnd.BOTTOM) {
+ if (fetchEnd == FetchEnd.BOTTOM) {
mode = TimelineRequestMode.ANY;
} else {
mode = TimelineRequestMode.NETWORK;
}
- timelineRepo.getStatuses(maxId, sinceId, sinceIdMinusOne, LOAD_AT_ONCE, mode)
- .observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
- .subscribe((result) -> onFetchTimelineSuccess(result, fetchEnd, pos),
- (err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos));
+ timelineRepo
+ .getStatuses(maxId, sinceId, sinceIdMinusOne, LOAD_AT_ONCE, mode)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ (result) -> onFetchTimelineSuccess(result, fetchEnd, pos),
+ (err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos));
} else {
- Callback<List<Status>> callback = new Callback<List<Status>>() {
- @Override
- public void onResponse(@NonNull Call<List<Status>> call,
- @NonNull Response<List<Status>> response)
- {
- if(response.isSuccessful()) {
- @Nullable String newNextId = extractNextId(response);
- if(newNextId != null) {
- // when we reach the bottom of the list, we won't have a new link. If
- // we blindly write `null` here we will start loading from the top
- // again.
- nextId = newNextId;
+ Callback<List<Status>> callback =
+ new Callback<List<Status>>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<List<Status>> call,
+ @NonNull Response<List<Status>> response) {
+ if (response.isSuccessful()) {
+ @Nullable String newNextId = extractNextId(response);
+ if (newNextId != null) {
+ // when we reach the bottom of the list, we won't have a new
+ // link. If
+ // we blindly write `null` here we will start loading from the
+ // top
+ // again.
+ nextId = newNextId;
+ }
+ onFetchTimelineSuccess(
+ liftStatusList(response.body()), fetchEnd, pos);
+ } else {
+ onFetchTimelineFailure(
+ new Exception(response.message()), fetchEnd, pos);
+ }
}
- onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos);
- } else {
- onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos);
- }
- }
- @Override
- public void onFailure(@NonNull Call<List<Status>> call, @NonNull Throwable t) {
- onFetchTimelineFailure((Exception) t, fetchEnd, pos);
- }
- };
+ @Override
+ public void onFailure(
+ @NonNull Call<List<Status>> call, @NonNull Throwable t) {
+ onFetchTimelineFailure((Exception) t, fetchEnd, pos);
+ }
+ };
Call<List<Status>> listCall = getFetchCallByTimelineType(maxId, sinceId);
callList.add(listCall);
@@ -1140,70 +1259,72 @@ public class TimelineFragment extends SFragment
@Nullable
private String extractNextId(Response<?> response) {
String linkHeader = response.headers().get("Link");
- if(linkHeader == null) {
+ if (linkHeader == null) {
return null;
}
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
HttpHeaderLink nextHeader = HttpHeaderLink.findByRelationType(links, "next");
- if(nextHeader == null) {
+ if (nextHeader == null) {
return null;
}
Uri nextLink = nextHeader.uri;
- if(nextLink == null) {
+ if (nextLink == null) {
return null;
}
return nextLink.getQueryParameter("max_id");
}
- private void onFetchTimelineSuccess(List<Either<Placeholder, Status>> statuses,
- FetchEnd fetchEnd, int pos)
- {
+ private void onFetchTimelineSuccess(
+ List<Either<Placeholder, Status>> statuses, FetchEnd fetchEnd, int pos) {
// We filled the hole (or reached the end) if the server returned less statuses than we
// we asked for.
boolean fullFetch = statuses.size() >= LOAD_AT_ONCE;
filterStatuses(statuses);
- switch(fetchEnd) {
- case TOP: {
- updateStatuses(statuses, fullFetch);
- break;
- }
- case MIDDLE: {
- replacePlaceholderWithStatuses(statuses, fullFetch, pos);
- break;
- }
- case BOTTOM: {
- if(!this.statuses.isEmpty() &&
- !this.statuses.get(this.statuses.size() - 1).isRight()) {
- this.statuses.remove(this.statuses.size() - 1);
- updateAdapter();
- }
-
- if(!statuses.isEmpty() && !statuses.get(statuses.size() - 1).isRight()) {
- // Removing placeholder if it's the last one from the cache
- statuses.remove(statuses.size() - 1);
- }
- int oldSize = this.statuses.size();
- if(this.statuses.size() > 1) {
- addItems(statuses);
- } else {
+ switch (fetchEnd) {
+ case TOP:
+ {
updateStatuses(statuses, fullFetch);
+ break;
}
- if(this.statuses.size() == oldSize) {
- // This may be a brittle check but seems like it works
- // Can we check it using headers somehow? Do all server support them?
- didLoadEverythingBottom = true;
+ case MIDDLE:
+ {
+ replacePlaceholderWithStatuses(statuses, fullFetch, pos);
+ break;
+ }
+ case BOTTOM:
+ {
+ if (!this.statuses.isEmpty()
+ && !this.statuses.get(this.statuses.size() - 1).isRight()) {
+ this.statuses.remove(this.statuses.size() - 1);
+ updateAdapter();
+ }
+
+ if (!statuses.isEmpty() && !statuses.get(statuses.size() - 1).isRight()) {
+ // Removing placeholder if it's the last one from the cache
+ statuses.remove(statuses.size() - 1);
+ }
+ int oldSize = this.statuses.size();
+ if (this.statuses.size() > 1) {
+ addItems(statuses);
+ } else {
+ updateStatuses(statuses, fullFetch);
+ }
+ if (this.statuses.size() == oldSize) {
+ // This may be a brittle check but seems like it works
+ // Can we check it using headers somehow? Do all server support them?
+ didLoadEverythingBottom = true;
+ }
+ break;
}
- break;
- }
}
- if(isAdded()) {
+ if (isAdded()) {
topProgressBar.hide();
updateBottomLoadingState(fetchEnd);
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
swipeRefreshLayout.setEnabled(true);
- if(this.statuses.size() == 0) {
+ if (this.statuses.size() == 0) {
this.showNothing();
} else {
this.statusView.setVisibility(View.GONE);
@@ -1212,14 +1333,14 @@ public class TimelineFragment extends SFragment
}
private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) {
- if(isAdded()) {
+ if (isAdded()) {
swipeRefreshLayout.setRefreshing(false);
topProgressBar.hide();
- if(fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) {
+ if (fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) {
Placeholder placeholder = statuses.get(position).asLeftOrNull();
StatusViewData newViewData;
- if(placeholder == null) {
+ if (placeholder == null) {
Status above = statuses.get(position - 1).asRight();
String newId = StringUtils.dec(above.getId());
placeholder = new Placeholder(newId);
@@ -1227,22 +1348,27 @@ public class TimelineFragment extends SFragment
newViewData = new StatusViewData.Placeholder(placeholder.getId(), false);
statuses.setPairedItem(position, newViewData);
updateAdapter();
- } else if(this.statuses.isEmpty()) {
+ } else if (this.statuses.isEmpty()) {
swipeRefreshLayout.setEnabled(false);
this.statusView.setVisibility(View.VISIBLE);
- if(exception instanceof IOException) {
- this.statusView.setup(R.drawable.elephant_offline, R.string.error_network,
- __ -> {
- this.progressBar.setVisibility(View.VISIBLE);
- this.onRefresh();
- return Unit.INSTANCE;
- });
+ if (exception instanceof IOException) {
+ this.statusView.setup(
+ R.drawable.elephant_offline,
+ R.string.error_network,
+ __ -> {
+ this.progressBar.setVisibility(View.VISIBLE);
+ this.onRefresh();
+ return Unit.INSTANCE;
+ });
} else {
- this.statusView.setup(R.drawable.elephant_error, R.string.error_generic, __ -> {
- this.progressBar.setVisibility(View.VISIBLE);
- this.onRefresh();
- return Unit.INSTANCE;
- });
+ this.statusView.setup(
+ R.drawable.elephant_error,
+ R.string.error_generic,
+ __ -> {
+ this.progressBar.setVisibility(View.VISIBLE);
+ this.onRefresh();
+ return Unit.INSTANCE;
+ });
}
}
@@ -1253,44 +1379,48 @@ public class TimelineFragment extends SFragment
}
private void updateBottomLoadingState(FetchEnd fetchEnd) {
- if(fetchEnd == FetchEnd.BOTTOM) {
+ if (fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = false;
}
}
private void filterStatuses(List<Either<Placeholder, Status>> statuses) {
Iterator<Either<Placeholder, Status>> it = statuses.iterator();
- while(it.hasNext()) {
+ while (it.hasNext()) {
Status status = it.next().asRightOrNull();
- if(status != null && ((status.getInReplyToId() != null && filterRemoveReplies) ||
- (status.getReblog() != null && filterRemoveReblogs) ||
- shouldFilterStatus(status.getActionableStatus()))) {
+ if (status != null
+ && ((status.getInReplyToId() != null && filterRemoveReplies)
+ || (status.getReblog() != null && filterRemoveReblogs)
+ || shouldFilterStatus(status.getActionableStatus()))) {
it.remove();
}
}
}
private void updateStatuses(List<Either<Placeholder, Status>> newStatuses, boolean fullFetch) {
- if(ListUtils.isEmpty(newStatuses)) {
+ if (ListUtils.isEmpty(newStatuses)) {
updateAdapter();
return;
}
- if(statuses.isEmpty()) {
+ if (statuses.isEmpty()) {
statuses.addAll(newStatuses);
} else {
Either<Placeholder, Status> lastOfNew = newStatuses.get(newStatuses.size() - 1);
int index = statuses.indexOf(lastOfNew);
- if(index >= 0) {
+ if (index >= 0) {
statuses.subList(0, index).clear();
}
int newIndex = newStatuses.indexOf(statuses.get(0));
- if(newIndex == -1) {
- if(index == -1 && fullFetch) {
- String placeholderId = StringUtils.inc(
- CollectionsKt.last(newStatuses, Either::isRight).asRight().getId());
+ if (newIndex == -1) {
+ if (index == -1 && fullFetch) {
+ String placeholderId =
+ StringUtils.inc(
+ CollectionsKt.last(newStatuses, Either::isRight)
+ .asRight()
+ .getId());
newStatuses.add(new Either.Left<>(new Placeholder(placeholderId)));
}
statuses.addAll(0, newStatuses);
@@ -1304,27 +1434,27 @@ public class TimelineFragment extends SFragment
}
private void removeConsecutivePlaceholders() {
- for(int i = 0; i < statuses.size() - 1; i++) {
- if(statuses.get(i).isLeft() && statuses.get(i + 1).isLeft()) {
+ for (int i = 0; i < statuses.size() - 1; i++) {
+ if (statuses.get(i).isLeft() && statuses.get(i + 1).isLeft()) {
statuses.remove(i);
}
}
}
private void addItems(List<Either<Placeholder, Status>> newStatuses) {
- if(ListUtils.isEmpty(newStatuses)) {
+ if (ListUtils.isEmpty(newStatuses)) {
return;
}
Either<Placeholder, Status> last = null;
- for(int i = statuses.size() - 1; i >= 0; i--) {
- if(statuses.get(i).isRight()) {
+ for (int i = statuses.size() - 1; i >= 0; i--) {
+ if (statuses.get(i).isRight()) {
last = statuses.get(i);
break;
}
}
// I was about to replace findStatus with indexOf but it is incorrect to compare value
// types by ID anyway and we should change equals() for Status, I think, so this makes sense
- if(last != null && !newStatuses.contains(last)) {
+ if (last != null && !newStatuses.contains(last)) {
statuses.addAll(newStatuses);
removeConsecutivePlaceholders();
updateAdapter();
@@ -1338,20 +1468,19 @@ public class TimelineFragment extends SFragment
CollectionsKt.removeAll(statuses, Either::isLeft);
}
- private void replacePlaceholderWithStatuses(List<Either<Placeholder, Status>> newStatuses,
- boolean fullFetch, int pos)
- {
+ private void replacePlaceholderWithStatuses(
+ List<Either<Placeholder, Status>> newStatuses, boolean fullFetch, int pos) {
Either<Placeholder, Status> placeholder = statuses.get(pos);
- if(placeholder.isLeft()) {
+ if (placeholder.isLeft()) {
statuses.remove(pos);
}
- if(ListUtils.isEmpty(newStatuses)) {
+ if (ListUtils.isEmpty(newStatuses)) {
updateAdapter();
return;
}
- if(fullFetch) {
+ if (fullFetch) {
newStatuses.add(placeholder);
}
@@ -1359,16 +1488,15 @@ public class TimelineFragment extends SFragment
removeConsecutivePlaceholders();
updateAdapter();
-
}
private int findStatusOrReblogPositionById(@NonNull String statusId) {
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
Status status = statuses.get(i).asRightOrNull();
- if(status != null && (statusId.equals(status.getId()) || (status.getReblog() != null &&
- statusId.equals(
- status.getReblog()
- .getId())))) {
+ if (status != null
+ && (statusId.equals(status.getId())
+ || (status.getReblog() != null
+ && statusId.equals(status.getReblog().getId())))) {
return i;
}
}
@@ -1378,19 +1506,18 @@ public class TimelineFragment extends SFragment
private final Function1<Status, Either<Placeholder, Status>> statusLifter = Either.Right::new;
@Nullable
- private Pair<StatusViewData.Concrete, Integer> findStatusAndPosition(int position,
- Status status)
- {
+ private Pair<StatusViewData.Concrete, Integer> findStatusAndPosition(
+ int position, Status status) {
StatusViewData.Concrete statusToUpdate;
int positionToUpdate;
StatusViewData someOldViewData = statuses.getPairedItem(position);
// Unlikely, but data could change between the request and response
- if((someOldViewData instanceof StatusViewData.Placeholder) ||
- !((StatusViewData.Concrete) someOldViewData).getId().equals(status.getId())) {
+ if ((someOldViewData instanceof StatusViewData.Placeholder)
+ || !((StatusViewData.Concrete) someOldViewData).getId().equals(status.getId())) {
// try to find the status we need to update
int foundPos = statuses.indexOf(new Either.Right<>(status));
- if(foundPos < 0) {
+ if (foundPos < 0) {
return null; // okay, it's hopeless, give up
}
statusToUpdate = ((StatusViewData.Concrete) statuses.getPairedItem(foundPos));
@@ -1404,7 +1531,7 @@ public class TimelineFragment extends SFragment
private void handleReblogEvent(@NonNull ReblogEvent reblogEvent) {
int pos = findStatusOrReblogPositionById(reblogEvent.getStatusId());
- if(pos < 0) {
+ if (pos < 0) {
return;
}
Status status = statuses.get(pos).asRight();
@@ -1413,7 +1540,7 @@ public class TimelineFragment extends SFragment
private void handleFavEvent(@NonNull FavoriteEvent favEvent) {
int pos = findStatusOrReblogPositionById(favEvent.getStatusId());
- if(pos < 0) {
+ if (pos < 0) {
return;
}
Status status = statuses.get(pos).asRight();
@@ -1422,7 +1549,7 @@ public class TimelineFragment extends SFragment
private void handleBookmarkEvent(@NonNull BookmarkEvent bookmarkEvent) {
int pos = findStatusOrReblogPositionById(bookmarkEvent.getStatusId());
- if(pos < 0) {
+ if (pos < 0) {
return;
}
Status status = statuses.get(pos).asRight();
@@ -1430,7 +1557,7 @@ public class TimelineFragment extends SFragment
}
private void handleStatusComposeEvent(@NonNull Status status) {
- switch(kind) {
+ switch (kind) {
case HOME:
case PUBLIC_FEDERATED:
case PUBLIC_BUBBLE:
@@ -1438,7 +1565,7 @@ public class TimelineFragment extends SFragment
break;
case USER:
case USER_WITH_REPLIES:
- if(status.getAccount().getId().equals(id)) {
+ if (status.getAccount().getId().equals(id)) {
break;
} else {
return;
@@ -1454,15 +1581,15 @@ public class TimelineFragment extends SFragment
private void handleMuteStatusEvent(MuteConversationEvent event) {
int pos = findStatusOrReblogPositionById(event.getStatusId());
- if(pos < 0) {
+ if (pos < 0) {
return;
}
Status eventStatus = statuses.get(pos).asRight();
String conversationId = eventStatus.getConversationId();
- if(conversationId.isEmpty()) { // invalid conversation ID
- if(isFilteringMuted()) {
+ if (conversationId.isEmpty()) { // invalid conversation ID
+ if (isFilteringMuted()) {
statuses.remove(pos);
} else {
setMutedStatusForStatus(pos, eventStatus, event.getMute(), event.getMute());
@@ -1470,12 +1597,12 @@ public class TimelineFragment extends SFragment
updateAdapter();
} else {
//noinspection ConstantConditions
- if(isFilteringMuted()) {
+ if (isFilteringMuted()) {
removeAllByConversationId(conversationId);
} else {
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
Status status = statuses.get(i).asRightOrNull();
- if(status != null && status.getConversationId() == conversationId) {
+ if (status != null && status.getConversationId() == conversationId) {
setMutedStatusForStatus(i, status, event.getMute(), event.getMute());
}
}
@@ -1488,13 +1615,14 @@ public class TimelineFragment extends SFragment
String id = event.getAccountId();
boolean muting = event.getMute();
- if(isFilteringMuted() && muting) {
+ if (isFilteringMuted() && muting) {
removeAllByAccountId(id);
} else {
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
Status status = statuses.get(i).asRightOrNull();
- if(status != null && status.getAccount().getId().equals(id) &&
- !status.isThreadMuted()) {
+ if (status != null
+ && status.getAccount().getId().equals(id)
+ && !status.isThreadMuted()) {
setMutedStatusForStatus(i, status, muting, false);
}
}
@@ -1510,87 +1638,88 @@ public class TimelineFragment extends SFragment
differ.submitList(statuses.getPairedCopy());
}
- private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
- @Override
- public void onInserted(int position, int count) {
- if(isAdded()) {
- adapter.notifyItemRangeInserted(position, count);
- Context context = getContext();
- // scroll up when new items at the top are loaded while being in the first position
- // https://github.com/tuskyapp/Tusky/pull/1905#issuecomment-677819724
- if(position == 0 && context != null && adapter.getItemCount() != count) {
- if(isSwipeToRefreshEnabled) {
- recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
- } else {
- recyclerView.scrollToPosition(0);
+ private final ListUpdateCallback listUpdateCallback =
+ new ListUpdateCallback() {
+ @Override
+ public void onInserted(int position, int count) {
+ if (isAdded()) {
+ adapter.notifyItemRangeInserted(position, count);
+ Context context = getContext();
+ // scroll up when new items at the top are loaded while being in the first
+ // position
+ // https://github.com/tuskyapp/Tusky/pull/1905#issuecomment-677819724
+ if (position == 0 && context != null && adapter.getItemCount() != count) {
+ if (isSwipeToRefreshEnabled) {
+ recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
+ } else {
+ recyclerView.scrollToPosition(0);
+ }
+ }
}
}
- }
- }
-
- @Override
- public void onRemoved(int position, int count) {
- adapter.notifyItemRangeRemoved(position, count);
- }
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- adapter.notifyItemMoved(fromPosition, toPosition);
- }
+ @Override
+ public void onRemoved(int position, int count) {
+ adapter.notifyItemRangeRemoved(position, count);
+ }
- @Override
- public void onChanged(int position, int count, Object payload) {
- adapter.notifyItemRangeChanged(position, count, payload);
- }
- };
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ adapter.notifyItemMoved(fromPosition, toPosition);
+ }
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ adapter.notifyItemRangeChanged(position, count, payload);
+ }
+ };
- private final AsyncListDiffer<StatusViewData> differ = new AsyncListDiffer<>(listUpdateCallback,
- new AsyncDifferConfig.Builder<>(diffCallback).build());
+ private final AsyncListDiffer<StatusViewData> differ =
+ new AsyncListDiffer<>(
+ listUpdateCallback, new AsyncDifferConfig.Builder<>(diffCallback).build());
private final TimelineAdapter.AdapterDataSource<StatusViewData> dataSource =
- new TimelineAdapter.AdapterDataSource<StatusViewData>() {
- @Override
- public int getItemCount() {
- return differ.getCurrentList().size();
- }
+ new TimelineAdapter.AdapterDataSource<StatusViewData>() {
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
- @Override
- public StatusViewData getItemAt(int pos) {
- return differ.getCurrentList().get(pos);
- }
- };
+ @Override
+ public StatusViewData getItemAt(int pos) {
+ return differ.getCurrentList().get(pos);
+ }
+ };
private static final DiffUtil.ItemCallback<StatusViewData> diffCallback =
- new DiffUtil.ItemCallback<StatusViewData>() {
+ new DiffUtil.ItemCallback<StatusViewData>() {
- @Override
- public boolean areItemsTheSame(StatusViewData oldItem, StatusViewData newItem) {
- return oldItem.getViewDataId() == newItem.getViewDataId();
- }
+ @Override
+ public boolean areItemsTheSame(StatusViewData oldItem, StatusViewData newItem) {
+ return oldItem.getViewDataId() == newItem.getViewDataId();
+ }
- @Override
- public boolean areContentsTheSame(StatusViewData oldItem,
- @NonNull StatusViewData newItem)
- {
- return false; //Items are different always. It allows to refresh timestamp on every view holder update
- }
+ @Override
+ public boolean areContentsTheSame(
+ StatusViewData oldItem, @NonNull StatusViewData newItem) {
+ return false; // Items are different always. It allows to refresh timestamp on
+ // every view holder update
+ }
- @Nullable
- @Override
- public Object getChangePayload(@NonNull StatusViewData oldItem,
- @NonNull StatusViewData newItem)
- {
- if(oldItem.deepEquals(newItem)) {
- //If items are equal - update timestamp only
- return Collections.singletonList(StatusBaseViewHolder.Key.KEY_CREATED);
- } else
- // If items are different - update a whole view holder
- {
- return null;
+ @Nullable
+ @Override
+ public Object getChangePayload(
+ @NonNull StatusViewData oldItem, @NonNull StatusViewData newItem) {
+ if (oldItem.deepEquals(newItem)) {
+ // If items are equal - update timestamp only
+ return Collections.singletonList(StatusBaseViewHolder.Key.KEY_CREATED);
+ } else
+ // If items are different - update a whole view holder
+ {
+ return null;
+ }
}
- }
- };
+ };
@Override
public void onResume() {
@@ -1599,20 +1728,19 @@ public class TimelineFragment extends SFragment
}
/**
- * Start to update adapter every minute to refresh timestamp
- * If setting absoluteTimeView is false
- * Auto dispose observable on pause
+ * Start to update adapter every minute to refresh timestamp If setting absoluteTimeView is
+ * false Auto dispose observable on pause
*/
private void startUpdateTimestamp() {
SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
+ PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
- if(!useAbsoluteTime) {
- Observable.interval(1, TimeUnit.MINUTES).observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_PAUSE)))
- .subscribe(interval -> updateAdapter());
+ if (!useAbsoluteTime) {
+ Observable.interval(1, TimeUnit.MINUTES)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_PAUSE)))
+ .subscribe(interval -> updateAdapter());
}
-
}
@Override
@@ -1622,7 +1750,7 @@ public class TimelineFragment extends SFragment
@Override
public void refreshContent() {
- if(isAdded()) {
+ if (isAdded()) {
onRefresh();
} else {
isNeedRefresh = true;
@@ -1637,7 +1765,7 @@ public class TimelineFragment extends SFragment
private void setEmojiReactForStatus(int position, Status status, Status newStatus) {
Pair<StatusViewData.Concrete, Integer> actual = findStatusAndPosition(position, status);
- if(actual == null) {
+ if (actual == null) {
return;
}
@@ -1646,7 +1774,7 @@ public class TimelineFragment extends SFragment
public void handleEmojiReactEvent(EmojiReactEvent event) {
int pos = findStatusOrReblogPositionById(event.getNewStatus().getActionableId());
- if(pos < 0) {
+ if (pos < 0) {
return;
}
Status status = statuses.get(pos).asRight();
@@ -1656,21 +1784,29 @@ public class TimelineFragment extends SFragment
@Override
public void onEmojiReact(final boolean react, final String emoji, final String statusId) {
int position = findStatusOrReblogPositionById(statusId);
- if(position < 0) {
+ if (position < 0) {
return;
}
- timelineCases.getValue().react(emoji, statusId, react)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setEmojiReactionForStatus(position, newStatus),
- (t) -> Timber.e(t, "Failed to react with " + emoji + " on status: " + statusId));
-
+ timelineCases
+ .getValue()
+ .react(emoji, statusId, react)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setEmojiReactionForStatus(position, newStatus),
+ (t) ->
+ Timber.e(
+ t,
+ "Failed to react with "
+ + emoji
+ + " on status: "
+ + statusId));
}
@Override
- public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji,
- final String statusId)
- {
+ public void onEmojiReactMenu(
+ @NonNull View view, final EmojiReaction emoji, final String statusId) {
super.emojiReactMenu(statusId, emoji, view, this);
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
index f57ac2f..13faab2 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
@@ -15,6 +15,10 @@
package com.keylesspalace.tusky.fragment;
+import static com.uber.autodispose.AutoDispose.autoDisposable;
+import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -65,21 +69,17 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.ViewDataUtils;
import com.keylesspalace.tusky.view.ConversationLineItemDecoration;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import static com.uber.autodispose.AutoDispose.autoDisposable;
-import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import static org.koin.java.KoinJavaComponent.inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public final class ViewThreadFragment extends SFragment
- implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener
-{
+ implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
private static final String TAG = "ViewThreadFragment";
@@ -95,13 +95,14 @@ public final class ViewThreadFragment extends SFragment
private int statusIndex = 0;
private final PairedList<Status, StatusViewData.Concrete> statuses =
- new PairedList<>(new Function<Status, StatusViewData.Concrete>() {
- @Override
- public StatusViewData.Concrete apply(Status input) {
- return ViewDataUtils.statusToViewData(input, alwaysShowSensitiveMedia,
- alwaysOpenSpoiler);
- }
- });
+ new PairedList<>(
+ new Function<Status, StatusViewData.Concrete>() {
+ @Override
+ public StatusViewData.Concrete apply(Status input) {
+ return ViewDataUtils.statusToViewData(
+ input, alwaysShowSensitiveMedia, alwaysOpenSpoiler);
+ }
+ });
public static ViewThreadFragment newInstance(String id) {
Bundle arguments = new Bundle(1);
@@ -117,25 +118,29 @@ public final class ViewThreadFragment extends SFragment
thisThreadsStatusId = getArguments().getString("id");
SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
+ PreferenceManager.getDefaultSharedPreferences(getActivity());
StatusDisplayOptions statusDisplayOptions =
- new StatusDisplayOptions(preferences.getBoolean("animateGifAvatars", false),
- accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
- preferences.getBoolean("absoluteTimeView", false),
- preferences.getBoolean("showBotOverlay", true),
- preferences.getBoolean("useBlurhash", true),
- preferences.getBoolean("showCardsInTimelines", false) ? CardViewMode.INDENTED :
- CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true),
- preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
- preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
+ new StatusDisplayOptions(
+ preferences.getBoolean("animateGifAvatars", false),
+ accountManager.getValue().getActiveAccount().getMediaPreviewEnabled(),
+ preferences.getBoolean("absoluteTimeView", false),
+ preferences.getBoolean("showBotOverlay", true),
+ preferences.getBoolean("useBlurhash", true),
+ preferences.getBoolean("showCardsInTimelines", false)
+ ? CardViewMode.INDENTED
+ : CardViewMode.NONE,
+ preferences.getBoolean("confirmReblogs", true),
+ preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
+ preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false));
adapter = new ThreadAdapter(statusDisplayOptions, this);
}
@Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState)
- {
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false);
Context context = getContext();
@@ -148,14 +153,15 @@ public final class ViewThreadFragment extends SFragment
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAccessibilityDelegateCompat(
- new ListStatusAccessibilityDelegate(recyclerView, this, statuses::getPairedItemOrNull));
+ new ListStatusAccessibilityDelegate(
+ recyclerView, this, statuses::getPairedItemOrNull));
DividerItemDecoration divider =
- new DividerItemDecoration(context, layoutManager.getOrientation());
+ new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(divider);
recyclerView.addItemDecoration(new ConversationLineItemDecoration(context));
alwaysShowSensitiveMedia =
- accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
+ accountManager.getValue().getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getValue().getActiveAccount().getAlwaysOpenSpoiler();
reloadFilters(PreferenceManager.getDefaultSharedPreferences(context), false);
@@ -168,44 +174,47 @@ public final class ViewThreadFragment extends SFragment
return rootView;
}
-
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
onRefresh();
- eventHub.getEvents().observeOn(AndroidSchedulers.mainThread())
- .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))).subscribe(event -> {
- if(event instanceof FavoriteEvent) {
- handleFavEvent((FavoriteEvent) event);
- } else if(event instanceof ReblogEvent) {
- handleReblogEvent((ReblogEvent) event);
- } else if(event instanceof BookmarkEvent) {
- handleBookmarkEvent((BookmarkEvent) event);
- } else if(event instanceof BlockEvent) {
- removeAllByAccountId(((BlockEvent) event).getAccountId());
- } else if(event instanceof MuteEvent) {
- handleMuteEvent((MuteEvent) event);
- } else if(event instanceof StatusComposedEvent) {
- handleStatusComposedEvent((StatusComposedEvent) event);
- } else if(event instanceof StatusDeletedEvent) {
- handleStatusDeletedEvent((StatusDeletedEvent) event);
- } else if(event instanceof EmojiReactEvent) {
- handleEmojiReactEvent((EmojiReactEvent) event);
- }
- });
-
- if(thisThreadsStatusPosition != -1) {
+ eventHub.getEvents()
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
+ .subscribe(
+ event -> {
+ if (event instanceof FavoriteEvent) {
+ handleFavEvent((FavoriteEvent) event);
+ } else if (event instanceof ReblogEvent) {
+ handleReblogEvent((ReblogEvent) event);
+ } else if (event instanceof BookmarkEvent) {
+ handleBookmarkEvent((BookmarkEvent) event);
+ } else if (event instanceof BlockEvent) {
+ removeAllByAccountId(((BlockEvent) event).getAccountId());
+ } else if (event instanceof MuteEvent) {
+ handleMuteEvent((MuteEvent) event);
+ } else if (event instanceof StatusComposedEvent) {
+ handleStatusComposedEvent((StatusComposedEvent) event);
+ } else if (event instanceof StatusDeletedEvent) {
+ handleStatusDeletedEvent((StatusDeletedEvent) event);
+ } else if (event instanceof EmojiReactEvent) {
+ handleEmojiReactEvent((EmojiReactEvent) event);
+ }
+ });
+
+ if (thisThreadsStatusPosition != -1) {
recyclerView.scrollToPosition(thisThreadsStatusPosition);
}
}
public void onRevealPressed() {
boolean allExpanded = allExpanded();
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
StatusViewData.Concrete newViewData =
- new StatusViewData.Concrete.Builder(statuses.getPairedItem(i)).setIsExpanded(
- !allExpanded).createStatusViewData();
+ new StatusViewData.Concrete.Builder(statuses.getPairedItem(i))
+ .setIsExpanded(!allExpanded)
+ .createStatusViewData();
statuses.setPairedItem(i, newViewData);
}
updateAdapter();
@@ -214,8 +223,8 @@ public final class ViewThreadFragment extends SFragment
private boolean allExpanded() {
boolean allExpanded = true;
- for(int i = 0; i < statuses.size(); i++) {
- if(!statuses.getPairedItem(i).isExpanded()) {
+ for (int i = 0; i < statuses.size(); i++) {
+ if (!statuses.getPairedItem(i).isExpanded()) {
allExpanded = false;
break;
}
@@ -238,48 +247,59 @@ public final class ViewThreadFragment extends SFragment
public void onReblog(final boolean reblog, final int position) {
final Status status = statuses.get(position);
- timelineCases.getValue().reblog(statuses.get(position), reblog)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> updateStatus(position, newStatus),
- (t) -> Log.d(TAG, "Failed to reblog status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .reblog(statuses.get(position), reblog)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> updateStatus(position, newStatus),
+ (t) -> Log.d(TAG, "Failed to reblog status: " + status.getId(), t));
}
@Override
public void onFavourite(final boolean favourite, final int position) {
final Status status = statuses.get(position);
- timelineCases.getValue().favourite(statuses.get(position), favourite)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> updateStatus(position, newStatus),
- (t) -> Log.d(TAG, "Failed to favourite status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .favourite(statuses.get(position), favourite)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> updateStatus(position, newStatus),
+ (t) -> Log.d(TAG, "Failed to favourite status: " + status.getId(), t));
}
@Override
public void onBookmark(final boolean bookmark, final int position) {
final Status status = statuses.get(position);
- timelineCases.getValue().bookmark(statuses.get(position), bookmark)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> updateStatus(position, newStatus),
- (t) -> Log.d(TAG, "Failed to bookmark status: " + status.getId(), t));
+ timelineCases
+ .getValue()
+ .bookmark(statuses.get(position), bookmark)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> updateStatus(position, newStatus),
+ (t) -> Log.d(TAG, "Failed to bookmark status: " + status.getId(), t));
}
private void updateStatus(int position, Status status) {
- if(position >= 0 && position < statuses.size()) {
+ if (position >= 0 && position < statuses.size()) {
Status actionableStatus = status.getActionableStatus();
StatusViewData.Concrete viewData =
- new StatusViewData.Builder(statuses.getPairedItem(position)).setReblogged(
- actionableStatus.getReblogged())
- .setReblogsCount(actionableStatus.getReblogsCount())
- .setFavourited(actionableStatus.getFavourited())
- .setBookmarked(actionableStatus.getBookmarked())
- .setFavouritesCount(actionableStatus.getFavouritesCount())
- .createStatusViewData();
+ new StatusViewData.Builder(statuses.getPairedItem(position))
+ .setReblogged(actionableStatus.getReblogged())
+ .setReblogsCount(actionableStatus.getReblogsCount())
+ .setFavourited(actionableStatus.getFavourited())
+ .setBookmarked(actionableStatus.getBookmarked())
+ .setFavouritesCount(actionableStatus.getFavouritesCount())
+ .createStatusViewData();
statuses.setPairedItem(position, viewData);
adapter.setItem(position, viewData, true);
-
}
}
@@ -297,7 +317,7 @@ public final class ViewThreadFragment extends SFragment
@Override
public void onViewThread(int position) {
Status status = statuses.get(position);
- if(thisThreadsStatusId.equals(status.getId())) {
+ if (thisThreadsStatusId.equals(status.getId())) {
// If already viewing this thread, don't reopen it.
return;
}
@@ -307,7 +327,7 @@ public final class ViewThreadFragment extends SFragment
@Override
public void onViewReplyTo(int position) {
Status status = statuses.get(position);
- if(thisThreadsStatusId.equals(status.getInReplyToId())) {
+ if (thisThreadsStatusId.equals(status.getInReplyToId())) {
return;
}
super.onShowReplyTo(status.getInReplyToId());
@@ -322,8 +342,9 @@ public final class ViewThreadFragment extends SFragment
@Override
public void onExpandedChange(boolean expanded, int position) {
StatusViewData.Concrete newViewData =
- new StatusViewData.Builder(statuses.getPairedItem(position)).setIsExpanded(expanded)
- .createStatusViewData();
+ new StatusViewData.Builder(statuses.getPairedItem(position))
+ .setIsExpanded(expanded)
+ .createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
updateRevealIcon();
@@ -331,24 +352,23 @@ public final class ViewThreadFragment extends SFragment
@Override
public void onContentHiddenChange(boolean isShowing, int position) {
- StatusViewData.Concrete newViewData = new StatusViewData.Builder(
- statuses.getPairedItem(position)).setIsShowingSensitiveContent(isShowing)
- .createStatusViewData();
+ StatusViewData.Concrete newViewData =
+ new StatusViewData.Builder(statuses.getPairedItem(position))
+ .setIsShowingSensitiveContent(isShowing)
+ .createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
}
@Override
- public void onLoadMore(int position) {
-
- }
+ public void onLoadMore(int position) {}
@Override
public void onShowReblogs(int position) {
String statusId = statuses.get(position).getId();
Intent intent =
- AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REBLOGGED,
- statusId);
+ AccountListActivity.newIntent(
+ getContext(), AccountListActivity.Type.REBLOGGED, statusId);
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
}
@@ -356,32 +376,38 @@ public final class ViewThreadFragment extends SFragment
public void onShowFavs(int position) {
String statusId = statuses.get(position).getId();
Intent intent =
- AccountListActivity.newIntent(getContext(), AccountListActivity.Type.FAVOURITED,
- statusId);
+ AccountListActivity.newIntent(
+ getContext(), AccountListActivity.Type.FAVOURITED, statusId);
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
}
@Override
public void onContentCollapsedChange(boolean isCollapsed, int position) {
- if(position < 0 || position >= statuses.size()) {
- Log.e(TAG,
- String.format("Tried to access out of bounds status position: %d of %d", position,
- statuses.size() - 1));
+ if (position < 0 || position >= statuses.size()) {
+ Log.e(
+ TAG,
+ String.format(
+ "Tried to access out of bounds status position: %d of %d",
+ position, statuses.size() - 1));
return;
}
StatusViewData.Concrete status = statuses.getPairedItem(position);
- if(status == null) {
+ if (status == null) {
// Statuses PairedList contains a base type of StatusViewData.Concrete and also doesn't
- // check for null values when adding values to it although this doesn't seem to be an issue.
- Log.e(TAG, String.format(
- "Expected StatusViewData.Concrete, got null instead at position: %d of %d",
- position, statuses.size() - 1));
+ // check for null values when adding values to it although this doesn't seem to be an
+ // issue.
+ Log.e(
+ TAG,
+ String.format(
+ "Expected StatusViewData.Concrete, got null instead at position: %d of"
+ + " %d",
+ position, statuses.size() - 1));
return;
}
StatusViewData.Concrete updatedStatus =
- new StatusViewData.Builder(status).setCollapsed(isCollapsed).createStatusViewData();
+ new StatusViewData.Builder(status).setCollapsed(isCollapsed).createStatusViewData();
statuses.setPairedItem(position, updatedStatus);
recyclerView.post(() -> adapter.setItem(position, updatedStatus, true));
}
@@ -398,8 +424,8 @@ public final class ViewThreadFragment extends SFragment
@Override
public void removeItem(int position) {
- if(position == statusIndex) {
- //the status got removed, close the activity
+ if (position == statusIndex) {
+ // the status got removed, close the activity
getActivity().finish();
}
statuses.remove(position);
@@ -411,11 +437,14 @@ public final class ViewThreadFragment extends SFragment
setVoteForPoll(position, status.getPoll().votedCopy(choices));
- timelineCases.getValue().voteInPoll(status, choices)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newPoll) -> setVoteForPoll(position, newPoll),
- (t) -> Log.d(TAG, "Failed to vote in poll: " + status.getId(), t));
-
+ timelineCases
+ .getValue()
+ .voteInPoll(status, choices)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newPoll) -> setVoteForPoll(position, newPoll),
+ (t) -> Log.d(TAG, "Failed to vote in poll: " + status.getId(), t));
}
private void setVoteForPoll(int position, Poll newPoll) {
@@ -423,7 +452,7 @@ public final class ViewThreadFragment extends SFragment
StatusViewData.Concrete viewData = statuses.getPairedItem(position);
StatusViewData.Concrete newViewData =
- new StatusViewData.Builder(viewData).setPoll(newPoll).createStatusViewData();
+ new StatusViewData.Builder(viewData).setPoll(newPoll).createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
}
@@ -434,21 +463,21 @@ public final class ViewThreadFragment extends SFragment
private void removeAllByAccountId(String accountId) {
Status status = null;
- if(!statuses.isEmpty()) {
+ if (!statuses.isEmpty()) {
status = statuses.get(statusIndex);
}
// using iterator to safely remove items while iterating
Iterator<Status> iterator = statuses.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
Status s = iterator.next();
- if(s.getAccount().getId().equals(accountId) ||
- s.getActionableStatus().getAccount().getId().equals(accountId)) {
+ if (s.getAccount().getId().equals(accountId)
+ || s.getActionableStatus().getAccount().getId().equals(accountId)) {
iterator.remove();
}
}
statusIndex = statuses.indexOf(status);
- if(statusIndex == -1) {
- //the status got removed, close the activity
+ if (statusIndex == -1) {
+ // the status got removed, close the activity
getActivity().finish();
return;
}
@@ -458,66 +487,73 @@ public final class ViewThreadFragment extends SFragment
private void sendStatusRequest(final String id) {
Call<Status> call = mastodonApi.getValue().status(id);
- call.enqueue(new Callback<Status>() {
- @Override
- public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
- if(response.isSuccessful()) {
- int position = setStatus(response.body());
- recyclerView.scrollToPosition(position);
- } else {
- onThreadRequestFailure(id);
- }
- }
-
- @Override
- public void onFailure(@NonNull Call<Status> call, @NonNull Throwable t) {
- onThreadRequestFailure(id);
- }
- });
+ call.enqueue(
+ new Callback<Status>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<Status> call, @NonNull Response<Status> response) {
+ if (response.isSuccessful()) {
+ int position = setStatus(response.body());
+ recyclerView.scrollToPosition(position);
+ } else {
+ onThreadRequestFailure(id);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call<Status> call, @NonNull Throwable t) {
+ onThreadRequestFailure(id);
+ }
+ });
callList.add(call);
}
private void sendThreadRequest(final String id) {
Call<StatusContext> call = mastodonApi.getValue().statusContext(id);
- call.enqueue(new Callback<StatusContext>() {
- @Override
- public void onResponse(@NonNull Call<StatusContext> call,
- @NonNull Response<StatusContext> response)
- {
- StatusContext context = response.body();
- if(response.isSuccessful() && context != null) {
- swipeRefreshLayout.setRefreshing(false);
- setContext(context.getAncestors(), context.getDescendants());
- } else {
- onThreadRequestFailure(id);
- }
- }
-
- @Override
- public void onFailure(@NonNull Call<StatusContext> call, @NonNull Throwable t) {
- onThreadRequestFailure(id);
- }
- });
+ call.enqueue(
+ new Callback<StatusContext>() {
+ @Override
+ public void onResponse(
+ @NonNull Call<StatusContext> call,
+ @NonNull Response<StatusContext> response) {
+ StatusContext context = response.body();
+ if (response.isSuccessful() && context != null) {
+ swipeRefreshLayout.setRefreshing(false);
+ setContext(context.getAncestors(), context.getDescendants());
+ } else {
+ onThreadRequestFailure(id);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call<StatusContext> call, @NonNull Throwable t) {
+ onThreadRequestFailure(id);
+ }
+ });
callList.add(call);
}
private void onThreadRequestFailure(final String id) {
View view = getView();
swipeRefreshLayout.setRefreshing(false);
- if(view != null) {
+ if (view != null) {
Snackbar.make(view, R.string.error_generic, Snackbar.LENGTH_LONG)
- .setAction(R.string.action_retry, v -> {
- sendThreadRequest(id);
- sendStatusRequest(id);
- }).show();
+ .setAction(
+ R.string.action_retry,
+ v -> {
+ sendThreadRequest(id);
+ sendStatusRequest(id);
+ })
+ .show();
} else {
Log.e(TAG, "Couldn't display thread fetch error message");
}
}
private int setStatus(Status status) {
- if(statuses.size() > 0 && statusIndex < statuses.size() &&
- statuses.get(statusIndex).equals(status)) {
+ if (statuses.size() > 0
+ && statusIndex < statuses.size()
+ && statuses.get(statusIndex).equals(status)) {
// Do not add this status on refresh, it's already in there.
statuses.set(statusIndex, status);
return statusIndex;
@@ -536,15 +572,15 @@ public final class ViewThreadFragment extends SFragment
// 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 oldSize = statuses.size();
- if(oldSize > 1) {
+ if (oldSize > 1) {
mainStatus = statuses.get(statusIndex);
statuses.clear();
adapter.clearItems();
}
ArrayList<Status> ancestors = new ArrayList<>();
- for(Status status : unfilteredAncestors) {
- if(!shouldFilterStatus(status)) {
+ for (Status status : unfilteredAncestors) {
+ if (!shouldFilterStatus(status)) {
ancestors.add(status);
}
}
@@ -554,17 +590,22 @@ public final class ViewThreadFragment extends SFragment
adapter.setDetailedStatusPosition(statusIndex);
statuses.addAll(0, ancestors);
List<StatusViewData.Concrete> ancestorsViewDatas =
- statuses.getPairedCopy().subList(0, statusIndex);
- if(BuildConfig.DEBUG && ancestors.size() != ancestorsViewDatas.size()) {
- String error = String.format(Locale.getDefault(),
- "Incorrectly got statusViewData sublist." +
- " ancestors.size == %d ancestorsViewDatas.size == %d," + " statuses.size == %d",
- ancestors.size(), ancestorsViewDatas.size(), statuses.size());
+ statuses.getPairedCopy().subList(0, statusIndex);
+ if (BuildConfig.DEBUG && ancestors.size() != ancestorsViewDatas.size()) {
+ String error =
+ String.format(
+ Locale.getDefault(),
+ "Incorrectly got statusViewData sublist."
+ + " ancestors.size == %d ancestorsViewDatas.size == %d,"
+ + " statuses.size == %d",
+ ancestors.size(),
+ ancestorsViewDatas.size(),
+ statuses.size());
throw new AssertionError(error);
}
adapter.addAll(0, ancestorsViewDatas);
- if(mainStatus != null) {
+ 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.
// Not filtering the main status, since the user explicitly chose to be here
@@ -575,8 +616,8 @@ public final class ViewThreadFragment extends SFragment
}
ArrayList<Status> descendants = new ArrayList<>();
- for(Status status : unfilteredDescendants) {
- if(!shouldFilterStatus(status)) {
+ for (Status status : unfilteredDescendants) {
+ if (!shouldFilterStatus(status)) {
descendants.add(status);
}
}
@@ -585,12 +626,18 @@ public final class ViewThreadFragment extends SFragment
statuses.addAll(descendants);
List<StatusViewData.Concrete> descendantsViewData;
descendantsViewData =
- statuses.getPairedCopy().subList(statuses.size() - descendants.size(), statuses.size());
- if(BuildConfig.DEBUG && descendants.size() != descendantsViewData.size()) {
- String error = String.format(Locale.getDefault(),
- "Incorrectly got statusViewData sublist." +
- " descendants.size == %d descendantsViewData.size == %d," + " statuses.size == %d",
- descendants.size(), descendantsViewData.size(), statuses.size());
+ statuses.getPairedCopy()
+ .subList(statuses.size() - descendants.size(), statuses.size());
+ if (BuildConfig.DEBUG && descendants.size() != descendantsViewData.size()) {
+ String error =
+ String.format(
+ Locale.getDefault(),
+ "Incorrectly got statusViewData sublist."
+ + " descendants.size == %d descendantsViewData.size == %d,"
+ + " statuses.size == %d",
+ descendants.size(),
+ descendantsViewData.size(),
+ statuses.size());
throw new AssertionError(error);
}
adapter.addAll(descendantsViewData);
@@ -599,7 +646,7 @@ public final class ViewThreadFragment extends SFragment
private void setMutedStatusForStatus(int position, Status status, boolean muted) {
StatusViewData.Builder statusViewData =
- new StatusViewData.Builder(statuses.getPairedItem(position));
+ new StatusViewData.Builder(statuses.getPairedItem(position));
statusViewData.setMuted(muted);
statuses.setPairedItem(position, statusViewData.createStatusViewData());
@@ -609,13 +656,14 @@ public final class ViewThreadFragment extends SFragment
String id = event.getAccountId();
boolean muting = event.getMute();
- if(isFilteringMuted()) {
+ if (isFilteringMuted()) {
removeAllByAccountId(id);
} else {
- for(int i = 0; i < statuses.size(); i++) {
+ for (int i = 0; i < statuses.size(); i++) {
Status status = statuses.get(i);
- if(status != null && status.getAccount().getId().equals(id) &&
- !status.isThreadMuted()) {
+ if (status != null
+ && status.getAccount().getId().equals(id)
+ && !status.isThreadMuted()) {
setMutedStatusForStatus(i, status, muting);
}
}
@@ -623,17 +671,16 @@ public final class ViewThreadFragment extends SFragment
}
}
-
private void handleFavEvent(FavoriteEvent event) {
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
- if(posAndStatus == null) {
+ if (posAndStatus == null) {
return;
}
boolean favourite = event.getFavourite();
posAndStatus.second.setFavourited(favourite);
- if(posAndStatus.second.getReblog() != null) {
+ if (posAndStatus.second.getReblog() != null) {
posAndStatus.second.getReblog().setFavourited(favourite);
}
@@ -650,14 +697,14 @@ public final class ViewThreadFragment extends SFragment
private void handleReblogEvent(ReblogEvent event) {
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
- if(posAndStatus == null) {
+ if (posAndStatus == null) {
return;
}
boolean reblog = event.getReblog();
posAndStatus.second.setReblogged(reblog);
- if(posAndStatus.second.getReblog() != null) {
+ if (posAndStatus.second.getReblog() != null) {
posAndStatus.second.getReblog().setReblogged(reblog);
}
@@ -674,14 +721,14 @@ public final class ViewThreadFragment extends SFragment
private void handleBookmarkEvent(BookmarkEvent event) {
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
- if(posAndStatus == null) {
+ if (posAndStatus == null) {
return;
}
boolean bookmark = event.getBookmark();
posAndStatus.second.setBookmarked(bookmark);
- if(posAndStatus.second.getReblog() != null) {
+ if (posAndStatus.second.getReblog() != null) {
posAndStatus.second.getReblog().setBookmarked(bookmark);
}
@@ -698,18 +745,18 @@ public final class ViewThreadFragment extends SFragment
private void handleStatusComposedEvent(StatusComposedEvent event) {
Status eventStatus = event.getStatus();
- if(eventStatus.getInReplyToId() == null) {
+ if (eventStatus.getInReplyToId() == null) {
return;
}
- if(eventStatus.getInReplyToId().equals(thisThreadsStatusId)) {
+ if (eventStatus.getInReplyToId().equals(thisThreadsStatusId)) {
insertStatus(eventStatus, statuses.size());
} else {
// If new status is a reply to some status in the thread, insert new status after it
// We only check statuses below main status, ones on top don't belong to this thread
- for(int i = statusIndex; i < statuses.size(); i++) {
+ for (int i = statusIndex; i < statuses.size(); i++) {
Status status = statuses.get(i);
- if(eventStatus.getInReplyToId().equals(status.getId())) {
+ if (eventStatus.getInReplyToId().equals(status.getId())) {
insertStatus(eventStatus, i + 1);
break;
}
@@ -722,26 +769,27 @@ public final class ViewThreadFragment extends SFragment
private void insertStatus(Status status, int at) {
statuses.add(at, status);
adapter.addItem(at, statuses.getPairedItem(at));
- if(status.getId().equals(thisThreadsStatusId)) {
+ if (status.getId().equals(thisThreadsStatusId)) {
thisThreadsStatusPosition = at;
}
}
private void handleStatusDeletedEvent(StatusDeletedEvent event) {
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
- if(posAndStatus == null) {
+ if (posAndStatus == null) {
return;
}
- @SuppressWarnings("ConstantConditions") int pos = posAndStatus.first;
+ @SuppressWarnings("ConstantConditions")
+ int pos = posAndStatus.first;
statuses.remove(pos);
adapter.removeItem(pos);
}
@Nullable
private Pair<Integer, Status> findStatusAndPos(@NonNull String statusId) {
- for(int i = 0; i < statuses.size(); i++) {
- if(statusId.equals(statuses.get(i).getId())) {
+ for (int i = 0; i < statuses.size(); i++) {
+ if (statusId.equals(statuses.get(i).getId())) {
return new Pair<>(i, statuses.get(i));
}
}
@@ -750,24 +798,26 @@ public final class ViewThreadFragment extends SFragment
private void updateRevealIcon() {
ViewThreadActivity activity = ((ViewThreadActivity) getActivity());
- if(activity == null) {
+ if (activity == null) {
return;
}
boolean hasAnyWarnings = false;
// Statuses are updated from the main thread so nothing should change while iterating
- for(int i = 0; i < statuses.size(); i++) {
- if(!TextUtils.isEmpty(statuses.get(i).getSpoilerText())) {
+ for (int i = 0; i < statuses.size(); i++) {
+ if (!TextUtils.isEmpty(statuses.get(i).getSpoilerText())) {
hasAnyWarnings = true;
break;
}
}
- if(!hasAnyWarnings) {
+ if (!hasAnyWarnings) {
activity.setRevealButtonState(ViewThreadActivity.REVEAL_BUTTON_HIDDEN);
return;
}
- activity.setRevealButtonState(allExpanded() ? ViewThreadActivity.REVEAL_BUTTON_HIDE :
- ViewThreadActivity.REVEAL_BUTTON_REVEAL);
+ activity.setRevealButtonState(
+ allExpanded()
+ ? ViewThreadActivity.REVEAL_BUTTON_HIDE
+ : ViewThreadActivity.REVEAL_BUTTON_REVEAL);
}
@Override
@@ -789,8 +839,8 @@ public final class ViewThreadFragment extends SFragment
public void handleEmojiReactEvent(EmojiReactEvent event) {
Pair<Integer, Status> posAndStatus =
- findStatusAndPos(event.getNewStatus().getActionableId());
- if(posAndStatus == null) {
+ findStatusAndPos(event.getNewStatus().getActionableId());
+ if (posAndStatus == null) {
return;
}
setEmojiReactionForStatus(posAndStatus.first, event.getNewStatus());
@@ -800,21 +850,28 @@ public final class ViewThreadFragment extends SFragment
public void onEmojiReact(final boolean react, final String emoji, final String statusId) {
Pair<Integer, Status> statusAndPos = findStatusAndPos(statusId);
- if(statusAndPos == null) {
+ if (statusAndPos == null) {
return;
}
int position = statusAndPos.first;
- timelineCases.getValue().react(emoji, statusId, react)
- .observeOn(AndroidSchedulers.mainThread()).as(autoDisposable(from(this)))
- .subscribe((newStatus) -> setEmojiReactionForStatus(position, newStatus),
- (t) -> Log.d(TAG, "Failed to react with " + emoji + " on status: " + statusId, t));
+ timelineCases
+ .getValue()
+ .react(emoji, statusId, react)
+ .observeOn(AndroidSchedulers.mainThread())
+ .as(autoDisposable(from(this)))
+ .subscribe(
+ (newStatus) -> setEmojiReactionForStatus(position, newStatus),
+ (t) ->
+ Log.d(
+ TAG,
+ "Failed to react with " + emoji + " on status: " + statusId,
+ t));
}
@Override
- public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji,
- final String statusId)
- {
+ public void onEmojiReactMenu(
+ @NonNull View view, final EmojiReaction emoji, final String statusId) {
super.emojiReactMenu(statusId, emoji, view, this);
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
index c353d0f..3bdedfc 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
@@ -17,7 +17,11 @@ package com.keylesspalace.tusky.interfaces;
public interface AccountActionListener {
void onViewAccount(String id);
- void onMute(final boolean mute, final String id, final int position, final boolean notifications);
+
+ void onMute(
+ final boolean mute, final String id, final int position, final boolean notifications);
+
void onBlock(final boolean block, final String id, final int position);
+
void onRespondToFollowRequest(final boolean accept, final String id, final int position);
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
index 90599b2..369703c 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
@@ -17,6 +17,8 @@ package com.keylesspalace.tusky.interfaces;
public interface LinkListener {
void onViewTag(String tag);
+
void onViewAccount(String id);
+
void onViewUrl(String url);
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java
index ca83e08..a87e87b 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/PermissionRequester.java
@@ -2,4 +2,4 @@ package com.keylesspalace.tusky.interfaces;
public interface PermissionRequester {
void onRequestPermissionsResult(String[] permissions, int[] grantResults);
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
index d956cec..22868f8 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
@@ -16,30 +16,39 @@
package com.keylesspalace.tusky.interfaces;
import android.view.View;
-
-import java.util.List;
-import com.keylesspalace.tusky.entity.EmojiReaction;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.keylesspalace.tusky.entity.EmojiReaction;
+import java.util.List;
public interface StatusActionListener extends LinkListener {
void onReply(int position);
+
void onReblog(final boolean reblog, final int position);
+
void onFavourite(final boolean favourite, final int position);
+
void onBookmark(final boolean bookmark, final int position);
+
void onMore(@NonNull View view, final int position);
+
void onViewMedia(int position, int attachmentIndex, @Nullable View view);
+
void onViewThread(int position);
+
void onViewReplyTo(int position);
/**
* Open reblog author for the status.
+ *
* @param position At which position in the list status is located
*/
void onOpenReblog(int position);
+
void onExpandedChange(boolean expanded, int position);
+
void onContentHiddenChange(boolean isShowing, int position);
+
void onLoadMore(int position);
/**
@@ -47,26 +56,37 @@ public interface StatusActionListener extends LinkListener {
* status content is interacted with.
*
* @param isCollapsed Whether the status content is shown in a collapsed state or fully.
- * @param position The position of the status in the list.
+ * @param position The position of the status in the list.
*/
void onContentCollapsedChange(boolean isCollapsed, int position);
/**
* called when the reblog count has been clicked
+ *
* @param position The position of the status in the list.
*/
default void onShowReblogs(int position) {}
/**
* called when the favourite count has been clicked
+ *
* @param position The position of the status in the list.
*/
default void onShowFavs(int position) {}
void onVoteInPoll(int position, @NonNull List<Integer> choices);
-
+
default void onMute(int position, boolean isMuted) {}
- default void onEmojiReact(@NonNull final boolean react, @NonNull final String emoji, @NonNull final String statusId) {};
- default void onEmojiReactMenu(@NonNull View view, @NonNull final EmojiReaction emoji, @NonNull final String statusId) {};
+ default void onEmojiReact(
+ @NonNull final boolean react,
+ @NonNull final String emoji,
+ @NonNull final String statusId) {}
+ ;
+
+ default void onEmojiReactMenu(
+ @NonNull View view,
+ @NonNull final EmojiReaction emoji,
+ @NonNull final String statusId) {}
+ ;
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java b/husky/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java
index cd44f6d..7427923 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java
@@ -43,21 +43,22 @@ public final class InstanceSwitchAuthInterceptor implements Interceptor {
Request originalRequest = chain.request();
// Only switch domains if the request comes from retrofit
- if(originalRequest.url().host().equals(MastodonApi.PLACEHOLDER_DOMAIN)) {
+ if (originalRequest.url().host().equals(MastodonApi.PLACEHOLDER_DOMAIN)) {
AccountEntity currentAccount = accountManager.getActiveAccount();
Request.Builder builder = originalRequest.newBuilder();
String instanceHeader = originalRequest.header(MastodonApi.DOMAIN_HEADER);
- if(instanceHeader != null) {
+ if (instanceHeader != null) {
// use domain explicitly specified in custom header
builder.url(swapHost(originalRequest.url(), instanceHeader));
builder.removeHeader(MastodonApi.DOMAIN_HEADER);
- } else if(currentAccount != null) {
- //use domain of current account
+ } else if (currentAccount != null) {
+ // use domain of current account
builder.url(swapHost(originalRequest.url(), currentAccount.getDomain()))
- .header("Authorization",
- String.format("Bearer %s", currentAccount.getAccessToken()));
+ .header(
+ "Authorization",
+ String.format("Bearer %s", currentAccount.getAccessToken()));
}
Request newRequest = builder.build();
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java b/husky/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java
index d559d35..b1faeba 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java
@@ -16,10 +16,8 @@
package com.keylesspalace.tusky.network;
import androidx.annotation.NonNull;
-
import java.io.IOException;
import java.io.InputStream;
-
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
@@ -36,7 +34,11 @@ public final class ProgressRequestBody extends RequestBody {
void onProgressUpdate(int percentage);
}
- public ProgressRequestBody(final InputStream content, long contentLength, final MediaType mediaType, final UploadCallback listener) {
+ public ProgressRequestBody(
+ final InputStream content,
+ long contentLength,
+ final MediaType mediaType,
+ final UploadCallback listener) {
this.content = content;
this.contentLength = contentLength;
this.mediaType = mediaType;
@@ -62,7 +64,7 @@ public final class ProgressRequestBody extends RequestBody {
try {
int read;
while ((read = content.read(buffer)) != -1) {
- uploadListener.onProgressUpdate((int)(100 * uploaded / contentLength));
+ uploadListener.onProgressUpdate((int) (100 * uploaded / contentLength));
uploaded += read;
sink.write(buffer, 0, read);
@@ -71,4 +73,4 @@ public final class ProgressRequestBody extends RequestBody {
content.close();
}
}
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/BBCodeEdit.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/BBCodeEdit.java
index f67e3ad..0e4af19 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/BBCodeEdit.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/BBCodeEdit.java
@@ -1,17 +1,15 @@
package com.keylesspalace.tusky.util;
-import androidx.annotation.IntDef;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import android.text.Editable;
import android.text.Selection;
-import android.text.Spannable;
import android.widget.EditText;
+import androidx.annotation.NonNull;
import me.thanel.markdownedit.SelectionUtils;
public class BBCodeEdit {
- private BBCodeEdit() { /* cannot be instantiated */ }
+ private BBCodeEdit() {
+ /* cannot be instantiated */
+ }
public static void addBold(@NonNull Editable text) {
HTMLEdit.surroundSelectionWith(text, "[b]", "[/b]");
@@ -61,7 +59,7 @@ public class BBCodeEdit {
public static void addLink(@NonNull EditText editText) {
addLink(editText.getText());
}
-
+
/**
* Inserts a markdown code block to the specified EditText at the currently selected position.
*
@@ -80,4 +78,3 @@ public class BBCodeEdit {
addCode(editText.getText());
}
}
-
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/CustomURLSpan.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/CustomURLSpan.java
index e772162..f563f9f 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/CustomURLSpan.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/CustomURLSpan.java
@@ -15,26 +15,27 @@ public class CustomURLSpan extends URLSpan {
super(src);
}
- public static final Parcelable.Creator<CustomURLSpan> CREATOR = new Parcelable.Creator<CustomURLSpan>() {
+ public static final Parcelable.Creator<CustomURLSpan> CREATOR =
+ new Parcelable.Creator<CustomURLSpan>() {
- @Override
- public CustomURLSpan createFromParcel(Parcel source) {
- return new CustomURLSpan(source);
- }
+ @Override
+ public CustomURLSpan createFromParcel(Parcel source) {
+ return new CustomURLSpan(source);
+ }
- @Override
- public CustomURLSpan[] newArray(int size) {
- return new CustomURLSpan[size];
- }
-
- };
+ @Override
+ public CustomURLSpan[] newArray(int size) {
+ return new CustomURLSpan[size];
+ }
+ };
@Override
public void onClick(View view) {
LinkHelper.openLink(getURL(), view.getContext());
}
- @Override public void updateDrawState(TextPaint ds) {
+ @Override
+ public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/Emojis.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/Emojis.java
index 61de258..3271e79 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/Emojis.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/Emojis.java
@@ -1,1084 +1,1084 @@
package com.keylesspalace.tusky.util;
// AUTOGENERATED
-public class Emojis
-{
- public static final String[][] EMOJIS = new String[][] {
-{ // # group: Smileys & Emotion
-"😀", // 1F600 ; fully-qualified # 😀 grinning face
-"😃", // 1F603 ; fully-qualified # 😃 grinning face with big eyes
-"😄", // 1F604 ; fully-qualified # 😄 grinning face with smiling eyes
-"😁", // 1F601 ; fully-qualified # 😁 beaming face with smiling eyes
-"😆", // 1F606 ; fully-qualified # 😆 grinning squinting face
-"😅", // 1F605 ; fully-qualified # 😅 grinning face with sweat
-"🤣", // 1F923 ; fully-qualified # 🤣 rolling on the floor laughing
-"😂", // 1F602 ; fully-qualified # 😂 face with tears of joy
-"🙂", // 1F642 ; fully-qualified # 🙂 slightly smiling face
-"🙃", // 1F643 ; fully-qualified # 🙃 upside-down face
-"😉", // 1F609 ; fully-qualified # 😉 winking face
-"😊", // 1F60A ; fully-qualified # 😊 smiling face with smiling eyes
-"😇", // 1F607 ; fully-qualified # 😇 smiling face with halo
-"🥰", // 1F970 ; fully-qualified # 🥰 smiling face with hearts
-"😍", // 1F60D ; fully-qualified # 😍 smiling face with heart-eyes
-"🤩", // 1F929 ; fully-qualified # 🤩 star-struck
-"😘", // 1F618 ; fully-qualified # 😘 face blowing a kiss
-"😗", // 1F617 ; fully-qualified # 😗 kissing face
-"😚", // 1F61A ; fully-qualified # 😚 kissing face with closed eyes
-"😙", // 1F619 ; fully-qualified # 😙 kissing face with smiling eyes
-"😋", // 1F60B ; fully-qualified # 😋 face savoring food
-"😛", // 1F61B ; fully-qualified # 😛 face with tongue
-"😜", // 1F61C ; fully-qualified # 😜 winking face with tongue
-"🤪", // 1F92A ; fully-qualified # 🤪 zany face
-"😝", // 1F61D ; fully-qualified # 😝 squinting face with tongue
-"🤑", // 1F911 ; fully-qualified # 🤑 money-mouth face
-"🤗", // 1F917 ; fully-qualified # 🤗 hugging face
-"🤭", // 1F92D ; fully-qualified # 🤭 face with hand over mouth
-"🤫", // 1F92B ; fully-qualified # 🤫 shushing face
-"🤔", // 1F914 ; fully-qualified # 🤔 thinking face
-"🤐", // 1F910 ; fully-qualified # 🤐 zipper-mouth face
-"🤨", // 1F928 ; fully-qualified # 🤨 face with raised eyebrow
-"😐", // 1F610 ; fully-qualified # 😐 neutral face
-"😑", // 1F611 ; fully-qualified # 😑 expressionless face
-"😶", // 1F636 ; fully-qualified # 😶 face without mouth
-"😏", // 1F60F ; fully-qualified # 😏 smirking face
-"😒", // 1F612 ; fully-qualified # 😒 unamused face
-"🙄", // 1F644 ; fully-qualified # 🙄 face with rolling eyes
-"😬", // 1F62C ; fully-qualified # 😬 grimacing face
-"🤥", // 1F925 ; fully-qualified # 🤥 lying face
-"😌", // 1F60C ; fully-qualified # 😌 relieved face
-"😔", // 1F614 ; fully-qualified # 😔 pensive face
-"😪", // 1F62A ; fully-qualified # 😪 sleepy face
-"🤤", // 1F924 ; fully-qualified # 🤤 drooling face
-"😴", // 1F634 ; fully-qualified # 😴 sleeping face
-"😷", // 1F637 ; fully-qualified # 😷 face with medical mask
-"🤒", // 1F912 ; fully-qualified # 🤒 face with thermometer
-"🤕", // 1F915 ; fully-qualified # 🤕 face with head-bandage
-"🤢", // 1F922 ; fully-qualified # 🤢 nauseated face
-"🤮", // 1F92E ; fully-qualified # 🤮 face vomiting
-"🤧", // 1F927 ; fully-qualified # 🤧 sneezing face
-"🥵", // 1F975 ; fully-qualified # 🥵 hot face
-"🥶", // 1F976 ; fully-qualified # 🥶 cold face
-"🥴", // 1F974 ; fully-qualified # 🥴 woozy face
-"😵", // 1F635 ; fully-qualified # 😵 dizzy face
-"🤯", // 1F92F ; fully-qualified # 🤯 exploding head
-"🤠", // 1F920 ; fully-qualified # 🤠 cowboy hat face
-"🥳", // 1F973 ; fully-qualified # 🥳 partying face
-"😎", // 1F60E ; fully-qualified # 😎 smiling face with sunglasses
-"🤓", // 1F913 ; fully-qualified # 🤓 nerd face
-"🧐", // 1F9D0 ; fully-qualified # 🧐 face with monocle
-"😕", // 1F615 ; fully-qualified # 😕 confused face
-"😟", // 1F61F ; fully-qualified # 😟 worried face
-"🙁", // 1F641 ; fully-qualified # 🙁 slightly frowning face
-"😮", // 1F62E ; fully-qualified # 😮 face with open mouth
-"😯", // 1F62F ; fully-qualified # 😯 hushed face
-"😲", // 1F632 ; fully-qualified # 😲 astonished face
-"😳", // 1F633 ; fully-qualified # 😳 flushed face
-"🥺", // 1F97A ; fully-qualified # 🥺 pleading face
-"😦", // 1F626 ; fully-qualified # 😦 frowning face with open mouth
-"😧", // 1F627 ; fully-qualified # 😧 anguished face
-"😨", // 1F628 ; fully-qualified # 😨 fearful face
-"😰", // 1F630 ; fully-qualified # 😰 anxious face with sweat
-"😥", // 1F625 ; fully-qualified # 😥 sad but relieved face
-"😢", // 1F622 ; fully-qualified # 😢 crying face
-"😭", // 1F62D ; fully-qualified # 😭 loudly crying face
-"😱", // 1F631 ; fully-qualified # 😱 face screaming in fear
-"😖", // 1F616 ; fully-qualified # 😖 confounded face
-"😣", // 1F623 ; fully-qualified # 😣 persevering face
-"😞", // 1F61E ; fully-qualified # 😞 disappointed face
-"😓", // 1F613 ; fully-qualified # 😓 downcast face with sweat
-"😩", // 1F629 ; fully-qualified # 😩 weary face
-"😫", // 1F62B ; fully-qualified # 😫 tired face
-"🥱", // 1F971 ; fully-qualified # 🥱 yawning face
-"😤", // 1F624 ; fully-qualified # 😤 face with steam from nose
-"😡", // 1F621 ; fully-qualified # 😡 pouting face
-"😠", // 1F620 ; fully-qualified # 😠 angry face
-"🤬", // 1F92C ; fully-qualified # 🤬 face with symbols on mouth
-"😈", // 1F608 ; fully-qualified # 😈 smiling face with horns
-"👿", // 1F47F ; fully-qualified # 👿 angry face with horns
-"💀", // 1F480 ; fully-qualified # 💀 skull
-"💩", // 1F4A9 ; fully-qualified # 💩 pile of poo
-"🤡", // 1F921 ; fully-qualified # 🤡 clown face
-"👹", // 1F479 ; fully-qualified # 👹 ogre
-"👺", // 1F47A ; fully-qualified # 👺 goblin
-"👻", // 1F47B ; fully-qualified # 👻 ghost
-"👽", // 1F47D ; fully-qualified # 👽 alien
-"👾", // 1F47E ; fully-qualified # 👾 alien monster
-"🤖", // 1F916 ; fully-qualified # 🤖 robot
-"😺", // 1F63A ; fully-qualified # 😺 grinning cat
-"😸", // 1F638 ; fully-qualified # 😸 grinning cat with smiling eyes
-"😹", // 1F639 ; fully-qualified # 😹 cat with tears of joy
-"😻", // 1F63B ; fully-qualified # 😻 smiling cat with heart-eyes
-"😼", // 1F63C ; fully-qualified # 😼 cat with wry smile
-"😽", // 1F63D ; fully-qualified # 😽 kissing cat
-"🙀", // 1F640 ; fully-qualified # 🙀 weary cat
-"😿", // 1F63F ; fully-qualified # 😿 crying cat
-"😾", // 1F63E ; fully-qualified # 😾 pouting cat
-"🙈", // 1F648 ; fully-qualified # 🙈 see-no-evil monkey
-"🙉", // 1F649 ; fully-qualified # 🙉 hear-no-evil monkey
-"🙊", // 1F64A ; fully-qualified # 🙊 speak-no-evil monkey
-"💋", // 1F48B ; fully-qualified # 💋 kiss mark
-"💌", // 1F48C ; fully-qualified # 💌 love letter
-"💘", // 1F498 ; fully-qualified # 💘 heart with arrow
-"💝", // 1F49D ; fully-qualified # 💝 heart with ribbon
-"💖", // 1F496 ; fully-qualified # 💖 sparkling heart
-"💗", // 1F497 ; fully-qualified # 💗 growing heart
-"💓", // 1F493 ; fully-qualified # 💓 beating heart
-"💞", // 1F49E ; fully-qualified # 💞 revolving hearts
-"💕", // 1F495 ; fully-qualified # 💕 two hearts
-"💟", // 1F49F ; fully-qualified # 💟 heart decoration
-"💔", // 1F494 ; fully-qualified # 💔 broken heart
-"🧡", // 1F9E1 ; fully-qualified # 🧡 orange heart
-"💛", // 1F49B ; fully-qualified # 💛 yellow heart
-"💚", // 1F49A ; fully-qualified # 💚 green heart
-"💙", // 1F499 ; fully-qualified # 💙 blue heart
-"💜", // 1F49C ; fully-qualified # 💜 purple heart
-"🤎", // 1F90E ; fully-qualified # 🤎 brown heart
-"🖤", // 1F5A4 ; fully-qualified # 🖤 black heart
-"🤍", // 1F90D ; fully-qualified # 🤍 white heart
-"💯", // 1F4AF ; fully-qualified # 💯 hundred points
-"💢", // 1F4A2 ; fully-qualified # 💢 anger symbol
-"💥", // 1F4A5 ; fully-qualified # 💥 collision
-"💫", // 1F4AB ; fully-qualified # 💫 dizzy
-"💦", // 1F4A6 ; fully-qualified # 💦 sweat droplets
-"💨", // 1F4A8 ; fully-qualified # 💨 dashing away
-"💣", // 1F4A3 ; fully-qualified # 💣 bomb
-"💬", // 1F4AC ; fully-qualified # 💬 speech balloon
-"💭", // 1F4AD ; fully-qualified # 💭 thought balloon
-"💤", // 1F4A4 ; fully-qualified # 💤 zzz
-},
-{ // # group: People & Body
-"👋", // 1F44B ; fully-qualified # 👋 waving hand
-"🤚", // 1F91A ; fully-qualified # 🤚 raised back of hand
-"✋", // 270B ; fully-qualified # ✋ raised hand
-"🖖", // 1F596 ; fully-qualified # 🖖 vulcan salute
-"👌", // 1F44C ; fully-qualified # 👌 OK hand
-"🤏", // 1F90F ; fully-qualified # 🤏 pinching hand
-"🤞", // 1F91E ; fully-qualified # 🤞 crossed fingers
-"🤟", // 1F91F ; fully-qualified # 🤟 love-you gesture
-"🤘", // 1F918 ; fully-qualified # 🤘 sign of the horns
-"🤙", // 1F919 ; fully-qualified # 🤙 call me hand
-"👈", // 1F448 ; fully-qualified # 👈 backhand index pointing left
-"👉", // 1F449 ; fully-qualified # 👉 backhand index pointing right
-"👆", // 1F446 ; fully-qualified # 👆 backhand index pointing up
-"🖕", // 1F595 ; fully-qualified # 🖕 middle finger
-"👇", // 1F447 ; fully-qualified # 👇 backhand index pointing down
-"👍", // 1F44D ; fully-qualified # 👍 thumbs up
-"👎", // 1F44E ; fully-qualified # 👎 thumbs down
-"✊", // 270A ; fully-qualified # ✊ raised fist
-"👊", // 1F44A ; fully-qualified # 👊 oncoming fist
-"🤛", // 1F91B ; fully-qualified # 🤛 left-facing fist
-"🤜", // 1F91C ; fully-qualified # 🤜 right-facing fist
-"👏", // 1F44F ; fully-qualified # 👏 clapping hands
-"🙌", // 1F64C ; fully-qualified # 🙌 raising hands
-"👐", // 1F450 ; fully-qualified # 👐 open hands
-"🤲", // 1F932 ; fully-qualified # 🤲 palms up together
-"🤝", // 1F91D ; fully-qualified # 🤝 handshake
-"🙏", // 1F64F ; fully-qualified # 🙏 folded hands
-"💅", // 1F485 ; fully-qualified # 💅 nail polish
-"🤳", // 1F933 ; fully-qualified # 🤳 selfie
-"💪", // 1F4AA ; fully-qualified # 💪 flexed biceps
-"🦾", // 1F9BE ; fully-qualified # 🦾 mechanical arm
-"🦿", // 1F9BF ; fully-qualified # 🦿 mechanical leg
-"🦵", // 1F9B5 ; fully-qualified # 🦵 leg
-"🦶", // 1F9B6 ; fully-qualified # 🦶 foot
-"👂", // 1F442 ; fully-qualified # 👂 ear
-"🦻", // 1F9BB ; fully-qualified # 🦻 ear with hearing aid
-"👃", // 1F443 ; fully-qualified # 👃 nose
-"🧠", // 1F9E0 ; fully-qualified # 🧠 brain
-"🦷", // 1F9B7 ; fully-qualified # 🦷 tooth
-"🦴", // 1F9B4 ; fully-qualified # 🦴 bone
-"👀", // 1F440 ; fully-qualified # 👀 eyes
-"👅", // 1F445 ; fully-qualified # 👅 tongue
-"👄", // 1F444 ; fully-qualified # 👄 mouth
-"👶", // 1F476 ; fully-qualified # 👶 baby
-"🧒", // 1F9D2 ; fully-qualified # 🧒 child
-"👦", // 1F466 ; fully-qualified # 👦 boy
-"👧", // 1F467 ; fully-qualified # 👧 girl
-"🧑", // 1F9D1 ; fully-qualified # 🧑 person
-"👱", // 1F471 ; fully-qualified # 👱 person: blond hair
-"👨", // 1F468 ; fully-qualified # 👨 man
-"🧔", // 1F9D4 ; fully-qualified # 🧔 man: beard
-"👩", // 1F469 ; fully-qualified # 👩 woman
-"🧓", // 1F9D3 ; fully-qualified # 🧓 older person
-"👴", // 1F474 ; fully-qualified # 👴 old man
-"👵", // 1F475 ; fully-qualified # 👵 old woman
-"🙍", // 1F64D ; fully-qualified # 🙍 person frowning
-"🙎", // 1F64E ; fully-qualified # 🙎 person pouting
-"🙅", // 1F645 ; fully-qualified # 🙅 person gesturing NO
-"🙆", // 1F646 ; fully-qualified # 🙆 person gesturing OK
-"💁", // 1F481 ; fully-qualified # 💁 person tipping hand
-"🙋", // 1F64B ; fully-qualified # 🙋 person raising hand
-"🧏", // 1F9CF ; fully-qualified # 🧏 deaf person
-"🙇", // 1F647 ; fully-qualified # 🙇 person bowing
-"🤦", // 1F926 ; fully-qualified # 🤦 person facepalming
-"🤷", // 1F937 ; fully-qualified # 🤷 person shrugging
-"👮", // 1F46E ; fully-qualified # 👮 police officer
-"💂", // 1F482 ; fully-qualified # 💂 guard
-"👷", // 1F477 ; fully-qualified # 👷 construction worker
-"🤴", // 1F934 ; fully-qualified # 🤴 prince
-"👸", // 1F478 ; fully-qualified # 👸 princess
-"👳", // 1F473 ; fully-qualified # 👳 person wearing turban
-"👲", // 1F472 ; fully-qualified # 👲 man with Chinese cap
-"🧕", // 1F9D5 ; fully-qualified # 🧕 woman with headscarf
-"🤵", // 1F935 ; fully-qualified # 🤵 man in tuxedo
-"👰", // 1F470 ; fully-qualified # 👰 bride with veil
-"🤰", // 1F930 ; fully-qualified # 🤰 pregnant woman
-"🤱", // 1F931 ; fully-qualified # 🤱 breast-feeding
-"👼", // 1F47C ; fully-qualified # 👼 baby angel
-"🎅", // 1F385 ; fully-qualified # 🎅 Santa Claus
-"🤶", // 1F936 ; fully-qualified # 🤶 Mrs. Claus
-"🦸", // 1F9B8 ; fully-qualified # 🦸 superhero
-"🦹", // 1F9B9 ; fully-qualified # 🦹 supervillain
-"🧙", // 1F9D9 ; fully-qualified # 🧙 mage
-"🧚", // 1F9DA ; fully-qualified # 🧚 fairy
-"🧛", // 1F9DB ; fully-qualified # 🧛 vampire
-"🧜", // 1F9DC ; fully-qualified # 🧜 merperson
-"🧝", // 1F9DD ; fully-qualified # 🧝 elf
-"🧞", // 1F9DE ; fully-qualified # 🧞 genie
-"🧟", // 1F9DF ; fully-qualified # 🧟 zombie
-"💆", // 1F486 ; fully-qualified # 💆 person getting massage
-"💇", // 1F487 ; fully-qualified # 💇 person getting haircut
-"🚶", // 1F6B6 ; fully-qualified # 🚶 person walking
-"🧍", // 1F9CD ; fully-qualified # 🧍 person standing
-"🧎", // 1F9CE ; fully-qualified # 🧎 person kneeling
-"🏃", // 1F3C3 ; fully-qualified # 🏃 person running
-"💃", // 1F483 ; fully-qualified # 💃 woman dancing
-"🕺", // 1F57A ; fully-qualified # 🕺 man dancing
-"👯", // 1F46F ; fully-qualified # 👯 people with bunny ears
-"🧖", // 1F9D6 ; fully-qualified # 🧖 person in steamy room
-"🧗", // 1F9D7 ; fully-qualified # 🧗 person climbing
-"🤺", // 1F93A ; fully-qualified # 🤺 person fencing
-"🏇", // 1F3C7 ; fully-qualified # 🏇 horse racing
-"🏂", // 1F3C2 ; fully-qualified # 🏂 snowboarder
-"🏄", // 1F3C4 ; fully-qualified # 🏄 person surfing
-"🚣", // 1F6A3 ; fully-qualified # 🚣 person rowing boat
-"🏊", // 1F3CA ; fully-qualified # 🏊 person swimming
-"🚴", // 1F6B4 ; fully-qualified # 🚴 person biking
-"🚵", // 1F6B5 ; fully-qualified # 🚵 person mountain biking
-"🤸", // 1F938 ; fully-qualified # 🤸 person cartwheeling
-"🤼", // 1F93C ; fully-qualified # 🤼 people wrestling
-"🤽", // 1F93D ; fully-qualified # 🤽 person playing water polo
-"🤾", // 1F93E ; fully-qualified # 🤾 person playing handball
-"🤹", // 1F939 ; fully-qualified # 🤹 person juggling
-"🧘", // 1F9D8 ; fully-qualified # 🧘 person in lotus position
-"🛀", // 1F6C0 ; fully-qualified # 🛀 person taking bath
-"🛌", // 1F6CC ; fully-qualified # 🛌 person in bed
-"👭", // 1F46D ; fully-qualified # 👭 women holding hands
-"👫", // 1F46B ; fully-qualified # 👫 woman and man holding hands
-"👬", // 1F46C ; fully-qualified # 👬 men holding hands
-"💏", // 1F48F ; fully-qualified # 💏 kiss
-"💑", // 1F491 ; fully-qualified # 💑 couple with heart
-"👪", // 1F46A ; fully-qualified # 👪 family
-"👤", // 1F464 ; fully-qualified # 👤 bust in silhouette
-"👥", // 1F465 ; fully-qualified # 👥 busts in silhouette
-"👣", // 1F463 ; fully-qualified # 👣 footprints
-},
-{ // # group: Animals & Nature
-"🐵", // 1F435 ; fully-qualified # 🐵 monkey face
-"🐒", // 1F412 ; fully-qualified # 🐒 monkey
-"🦍", // 1F98D ; fully-qualified # 🦍 gorilla
-"🦧", // 1F9A7 ; fully-qualified # 🦧 orangutan
-"🐶", // 1F436 ; fully-qualified # 🐶 dog face
-"🐕", // 1F415 ; fully-qualified # 🐕 dog
-"🦮", // 1F9AE ; fully-qualified # 🦮 guide dog
-"🐩", // 1F429 ; fully-qualified # 🐩 poodle
-"🐺", // 1F43A ; fully-qualified # 🐺 wolf
-"🦊", // 1F98A ; fully-qualified # 🦊 fox
-"🦝", // 1F99D ; fully-qualified # 🦝 raccoon
-"🐱", // 1F431 ; fully-qualified # 🐱 cat face
-"🐈", // 1F408 ; fully-qualified # 🐈 cat
-"🦁", // 1F981 ; fully-qualified # 🦁 lion
-"🐯", // 1F42F ; fully-qualified # 🐯 tiger face
-"🐅", // 1F405 ; fully-qualified # 🐅 tiger
-"🐆", // 1F406 ; fully-qualified # 🐆 leopard
-"🐴", // 1F434 ; fully-qualified # 🐴 horse face
-"🐎", // 1F40E ; fully-qualified # 🐎 horse
-"🦄", // 1F984 ; fully-qualified # 🦄 unicorn
-"🦓", // 1F993 ; fully-qualified # 🦓 zebra
-"🦌", // 1F98C ; fully-qualified # 🦌 deer
-"🐮", // 1F42E ; fully-qualified # 🐮 cow face
-"🐂", // 1F402 ; fully-qualified # 🐂 ox
-"🐃", // 1F403 ; fully-qualified # 🐃 water buffalo
-"🐄", // 1F404 ; fully-qualified # 🐄 cow
-"🐷", // 1F437 ; fully-qualified # 🐷 pig face
-"🐖", // 1F416 ; fully-qualified # 🐖 pig
-"🐗", // 1F417 ; fully-qualified # 🐗 boar
-"🐽", // 1F43D ; fully-qualified # 🐽 pig nose
-"🐏", // 1F40F ; fully-qualified # 🐏 ram
-"🐑", // 1F411 ; fully-qualified # 🐑 ewe
-"🐐", // 1F410 ; fully-qualified # 🐐 goat
-"🐪", // 1F42A ; fully-qualified # 🐪 camel
-"🐫", // 1F42B ; fully-qualified # 🐫 two-hump camel
-"🦙", // 1F999 ; fully-qualified # 🦙 llama
-"🦒", // 1F992 ; fully-qualified # 🦒 giraffe
-"🐘", // 1F418 ; fully-qualified # 🐘 elephant
-"🦏", // 1F98F ; fully-qualified # 🦏 rhinoceros
-"🦛", // 1F99B ; fully-qualified # 🦛 hippopotamus
-"🐭", // 1F42D ; fully-qualified # 🐭 mouse face
-"🐁", // 1F401 ; fully-qualified # 🐁 mouse
-"🐀", // 1F400 ; fully-qualified # 🐀 rat
-"🐹", // 1F439 ; fully-qualified # 🐹 hamster
-"🐰", // 1F430 ; fully-qualified # 🐰 rabbit face
-"🐇", // 1F407 ; fully-qualified # 🐇 rabbit
-"🦔", // 1F994 ; fully-qualified # 🦔 hedgehog
-"🦇", // 1F987 ; fully-qualified # 🦇 bat
-"🐻", // 1F43B ; fully-qualified # 🐻 bear
-"🐨", // 1F428 ; fully-qualified # 🐨 koala
-"🐼", // 1F43C ; fully-qualified # 🐼 panda
-"🦥", // 1F9A5 ; fully-qualified # 🦥 sloth
-"🦦", // 1F9A6 ; fully-qualified # 🦦 otter
-"🦨", // 1F9A8 ; fully-qualified # 🦨 skunk
-"🦘", // 1F998 ; fully-qualified # 🦘 kangaroo
-"🦡", // 1F9A1 ; fully-qualified # 🦡 badger
-"🐾", // 1F43E ; fully-qualified # 🐾 paw prints
-"🦃", // 1F983 ; fully-qualified # 🦃 turkey
-"🐔", // 1F414 ; fully-qualified # 🐔 chicken
-"🐓", // 1F413 ; fully-qualified # 🐓 rooster
-"🐣", // 1F423 ; fully-qualified # 🐣 hatching chick
-"🐤", // 1F424 ; fully-qualified # 🐤 baby chick
-"🐥", // 1F425 ; fully-qualified # 🐥 front-facing baby chick
-"🐦", // 1F426 ; fully-qualified # 🐦 bird
-"🐧", // 1F427 ; fully-qualified # 🐧 penguin
-"🦅", // 1F985 ; fully-qualified # 🦅 eagle
-"🦆", // 1F986 ; fully-qualified # 🦆 duck
-"🦢", // 1F9A2 ; fully-qualified # 🦢 swan
-"🦉", // 1F989 ; fully-qualified # 🦉 owl
-"🦩", // 1F9A9 ; fully-qualified # 🦩 flamingo
-"🦚", // 1F99A ; fully-qualified # 🦚 peacock
-"🦜", // 1F99C ; fully-qualified # 🦜 parrot
-"🐸", // 1F438 ; fully-qualified # 🐸 frog
-"🐊", // 1F40A ; fully-qualified # 🐊 crocodile
-"🐢", // 1F422 ; fully-qualified # 🐢 turtle
-"🦎", // 1F98E ; fully-qualified # 🦎 lizard
-"🐍", // 1F40D ; fully-qualified # 🐍 snake
-"🐲", // 1F432 ; fully-qualified # 🐲 dragon face
-"🐉", // 1F409 ; fully-qualified # 🐉 dragon
-"🦕", // 1F995 ; fully-qualified # 🦕 sauropod
-"🦖", // 1F996 ; fully-qualified # 🦖 T-Rex
-"🐳", // 1F433 ; fully-qualified # 🐳 spouting whale
-"🐋", // 1F40B ; fully-qualified # 🐋 whale
-"🐬", // 1F42C ; fully-qualified # 🐬 dolphin
-"🐟", // 1F41F ; fully-qualified # 🐟 fish
-"🐠", // 1F420 ; fully-qualified # 🐠 tropical fish
-"🐡", // 1F421 ; fully-qualified # 🐡 blowfish
-"🦈", // 1F988 ; fully-qualified # 🦈 shark
-"🐙", // 1F419 ; fully-qualified # 🐙 octopus
-"🐚", // 1F41A ; fully-qualified # 🐚 spiral shell
-"🐌", // 1F40C ; fully-qualified # 🐌 snail
-"🦋", // 1F98B ; fully-qualified # 🦋 butterfly
-"🐛", // 1F41B ; fully-qualified # 🐛 bug
-"🐜", // 1F41C ; fully-qualified # 🐜 ant
-"🐝", // 1F41D ; fully-qualified # 🐝 honeybee
-"🐞", // 1F41E ; fully-qualified # 🐞 lady beetle
-"🦗", // 1F997 ; fully-qualified # 🦗 cricket
-"🦂", // 1F982 ; fully-qualified # 🦂 scorpion
-"🦟", // 1F99F ; fully-qualified # 🦟 mosquito
-"🦠", // 1F9A0 ; fully-qualified # 🦠 microbe
-"💐", // 1F490 ; fully-qualified # 💐 bouquet
-"🌸", // 1F338 ; fully-qualified # 🌸 cherry blossom
-"💮", // 1F4AE ; fully-qualified # 💮 white flower
-"🌹", // 1F339 ; fully-qualified # 🌹 rose
-"🥀", // 1F940 ; fully-qualified # 🥀 wilted flower
-"🌺", // 1F33A ; fully-qualified # 🌺 hibiscus
-"🌻", // 1F33B ; fully-qualified # 🌻 sunflower
-"🌼", // 1F33C ; fully-qualified # 🌼 blossom
-"🌷", // 1F337 ; fully-qualified # 🌷 tulip
-"🌱", // 1F331 ; fully-qualified # 🌱 seedling
-"🌲", // 1F332 ; fully-qualified # 🌲 evergreen tree
-"🌳", // 1F333 ; fully-qualified # 🌳 deciduous tree
-"🌴", // 1F334 ; fully-qualified # 🌴 palm tree
-"🌵", // 1F335 ; fully-qualified # 🌵 cactus
-"🌾", // 1F33E ; fully-qualified # 🌾 sheaf of rice
-"🌿", // 1F33F ; fully-qualified # 🌿 herb
-"🍀", // 1F340 ; fully-qualified # 🍀 four leaf clover
-"🍁", // 1F341 ; fully-qualified # 🍁 maple leaf
-"🍂", // 1F342 ; fully-qualified # 🍂 fallen leaf
-"🍃", // 1F343 ; fully-qualified # 🍃 leaf fluttering in wind
-},
-{ // # group: Food & Drink
-"🍇", // 1F347 ; fully-qualified # 🍇 grapes
-"🍈", // 1F348 ; fully-qualified # 🍈 melon
-"🍉", // 1F349 ; fully-qualified # 🍉 watermelon
-"🍊", // 1F34A ; fully-qualified # 🍊 tangerine
-"🍋", // 1F34B ; fully-qualified # 🍋 lemon
-"🍌", // 1F34C ; fully-qualified # 🍌 banana
-"🍍", // 1F34D ; fully-qualified # 🍍 pineapple
-"🥭", // 1F96D ; fully-qualified # 🥭 mango
-"🍎", // 1F34E ; fully-qualified # 🍎 red apple
-"🍏", // 1F34F ; fully-qualified # 🍏 green apple
-"🍐", // 1F350 ; fully-qualified # 🍐 pear
-"🍑", // 1F351 ; fully-qualified # 🍑 peach
-"🍒", // 1F352 ; fully-qualified # 🍒 cherries
-"🍓", // 1F353 ; fully-qualified # 🍓 strawberry
-"🥝", // 1F95D ; fully-qualified # 🥝 kiwi fruit
-"🍅", // 1F345 ; fully-qualified # 🍅 tomato
-"🥥", // 1F965 ; fully-qualified # 🥥 coconut
-"🥑", // 1F951 ; fully-qualified # 🥑 avocado
-"🍆", // 1F346 ; fully-qualified # 🍆 eggplant
-"🥔", // 1F954 ; fully-qualified # 🥔 potato
-"🥕", // 1F955 ; fully-qualified # 🥕 carrot
-"🌽", // 1F33D ; fully-qualified # 🌽 ear of corn
-"🥒", // 1F952 ; fully-qualified # 🥒 cucumber
-"🥬", // 1F96C ; fully-qualified # 🥬 leafy green
-"🥦", // 1F966 ; fully-qualified # 🥦 broccoli
-"🧄", // 1F9C4 ; fully-qualified # 🧄 garlic
-"🧅", // 1F9C5 ; fully-qualified # 🧅 onion
-"🍄", // 1F344 ; fully-qualified # 🍄 mushroom
-"🥜", // 1F95C ; fully-qualified # 🥜 peanuts
-"🌰", // 1F330 ; fully-qualified # 🌰 chestnut
-"🍞", // 1F35E ; fully-qualified # 🍞 bread
-"🥐", // 1F950 ; fully-qualified # 🥐 croissant
-"🥖", // 1F956 ; fully-qualified # 🥖 baguette bread
-"🥨", // 1F968 ; fully-qualified # 🥨 pretzel
-"🥯", // 1F96F ; fully-qualified # 🥯 bagel
-"🥞", // 1F95E ; fully-qualified # 🥞 pancakes
-"🧇", // 1F9C7 ; fully-qualified # 🧇 waffle
-"🧀", // 1F9C0 ; fully-qualified # 🧀 cheese wedge
-"🍖", // 1F356 ; fully-qualified # 🍖 meat on bone
-"🍗", // 1F357 ; fully-qualified # 🍗 poultry leg
-"🥩", // 1F969 ; fully-qualified # 🥩 cut of meat
-"🥓", // 1F953 ; fully-qualified # 🥓 bacon
-"🍔", // 1F354 ; fully-qualified # 🍔 hamburger
-"🍟", // 1F35F ; fully-qualified # 🍟 french fries
-"🍕", // 1F355 ; fully-qualified # 🍕 pizza
-"🌭", // 1F32D ; fully-qualified # 🌭 hot dog
-"🥪", // 1F96A ; fully-qualified # 🥪 sandwich
-"🌮", // 1F32E ; fully-qualified # 🌮 taco
-"🌯", // 1F32F ; fully-qualified # 🌯 burrito
-"🥙", // 1F959 ; fully-qualified # 🥙 stuffed flatbread
-"🧆", // 1F9C6 ; fully-qualified # 🧆 falafel
-"🥚", // 1F95A ; fully-qualified # 🥚 egg
-"🍳", // 1F373 ; fully-qualified # 🍳 cooking
-"🥘", // 1F958 ; fully-qualified # 🥘 shallow pan of food
-"🍲", // 1F372 ; fully-qualified # 🍲 pot of food
-"🥣", // 1F963 ; fully-qualified # 🥣 bowl with spoon
-"🥗", // 1F957 ; fully-qualified # 🥗 green salad
-"🍿", // 1F37F ; fully-qualified # 🍿 popcorn
-"🧈", // 1F9C8 ; fully-qualified # 🧈 butter
-"🧂", // 1F9C2 ; fully-qualified # 🧂 salt
-"🥫", // 1F96B ; fully-qualified # 🥫 canned food
-"🍱", // 1F371 ; fully-qualified # 🍱 bento box
-"🍘", // 1F358 ; fully-qualified # 🍘 rice cracker
-"🍙", // 1F359 ; fully-qualified # 🍙 rice ball
-"🍚", // 1F35A ; fully-qualified # 🍚 cooked rice
-"🍛", // 1F35B ; fully-qualified # 🍛 curry rice
-"🍜", // 1F35C ; fully-qualified # 🍜 steaming bowl
-"🍝", // 1F35D ; fully-qualified # 🍝 spaghetti
-"🍠", // 1F360 ; fully-qualified # 🍠 roasted sweet potato
-"🍢", // 1F362 ; fully-qualified # 🍢 oden
-"🍣", // 1F363 ; fully-qualified # 🍣 sushi
-"🍤", // 1F364 ; fully-qualified # 🍤 fried shrimp
-"🍥", // 1F365 ; fully-qualified # 🍥 fish cake with swirl
-"🥮", // 1F96E ; fully-qualified # 🥮 moon cake
-"🍡", // 1F361 ; fully-qualified # 🍡 dango
-"🥟", // 1F95F ; fully-qualified # 🥟 dumpling
-"🥠", // 1F960 ; fully-qualified # 🥠 fortune cookie
-"🥡", // 1F961 ; fully-qualified # 🥡 takeout box
-"🦀", // 1F980 ; fully-qualified # 🦀 crab
-"🦞", // 1F99E ; fully-qualified # 🦞 lobster
-"🦐", // 1F990 ; fully-qualified # 🦐 shrimp
-"🦑", // 1F991 ; fully-qualified # 🦑 squid
-"🦪", // 1F9AA ; fully-qualified # 🦪 oyster
-"🍦", // 1F366 ; fully-qualified # 🍦 soft ice cream
-"🍧", // 1F367 ; fully-qualified # 🍧 shaved ice
-"🍨", // 1F368 ; fully-qualified # 🍨 ice cream
-"🍩", // 1F369 ; fully-qualified # 🍩 doughnut
-"🍪", // 1F36A ; fully-qualified # 🍪 cookie
-"🎂", // 1F382 ; fully-qualified # 🎂 birthday cake
-"🍰", // 1F370 ; fully-qualified # 🍰 shortcake
-"🧁", // 1F9C1 ; fully-qualified # 🧁 cupcake
-"🥧", // 1F967 ; fully-qualified # 🥧 pie
-"🍫", // 1F36B ; fully-qualified # 🍫 chocolate bar
-"🍬", // 1F36C ; fully-qualified # 🍬 candy
-"🍭", // 1F36D ; fully-qualified # 🍭 lollipop
-"🍮", // 1F36E ; fully-qualified # 🍮 custard
-"🍯", // 1F36F ; fully-qualified # 🍯 honey pot
-"🍼", // 1F37C ; fully-qualified # 🍼 baby bottle
-"🥛", // 1F95B ; fully-qualified # 🥛 glass of milk
-"☕", // 2615 ; fully-qualified # ☕ hot beverage
-"🍵", // 1F375 ; fully-qualified # 🍵 teacup without handle
-"🍶", // 1F376 ; fully-qualified # 🍶 sake
-"🍾", // 1F37E ; fully-qualified # 🍾 bottle with popping cork
-"🍷", // 1F377 ; fully-qualified # 🍷 wine glass
-"🍸", // 1F378 ; fully-qualified # 🍸 cocktail glass
-"🍹", // 1F379 ; fully-qualified # 🍹 tropical drink
-"🍺", // 1F37A ; fully-qualified # 🍺 beer mug
-"🍻", // 1F37B ; fully-qualified # 🍻 clinking beer mugs
-"🥂", // 1F942 ; fully-qualified # 🥂 clinking glasses
-"🥃", // 1F943 ; fully-qualified # 🥃 tumbler glass
-"🥤", // 1F964 ; fully-qualified # 🥤 cup with straw
-"🧃", // 1F9C3 ; fully-qualified # 🧃 beverage box
-"🧉", // 1F9C9 ; fully-qualified # 🧉 mate
-"🧊", // 1F9CA ; fully-qualified # 🧊 ice cube
-"🥢", // 1F962 ; fully-qualified # 🥢 chopsticks
-"🍴", // 1F374 ; fully-qualified # 🍴 fork and knife
-"🥄", // 1F944 ; fully-qualified # 🥄 spoon
-"🔪", // 1F52A ; fully-qualified # 🔪 kitchen knife
-"🏺", // 1F3FA ; fully-qualified # 🏺 amphora
-},
-{ // # group: Travel & Places
-"🌍", // 1F30D ; fully-qualified # 🌍 globe showing Europe-Africa
-"🌎", // 1F30E ; fully-qualified # 🌎 globe showing Americas
-"🌏", // 1F30F ; fully-qualified # 🌏 globe showing Asia-Australia
-"🌐", // 1F310 ; fully-qualified # 🌐 globe with meridians
-"🗾", // 1F5FE ; fully-qualified # 🗾 map of Japan
-"🧭", // 1F9ED ; fully-qualified # 🧭 compass
-"🌋", // 1F30B ; fully-qualified # 🌋 volcano
-"🗻", // 1F5FB ; fully-qualified # 🗻 mount fuji
-"🧱", // 1F9F1 ; fully-qualified # 🧱 brick
-"🏠", // 1F3E0 ; fully-qualified # 🏠 house
-"🏡", // 1F3E1 ; fully-qualified # 🏡 house with garden
-"🏢", // 1F3E2 ; fully-qualified # 🏢 office building
-"🏣", // 1F3E3 ; fully-qualified # 🏣 Japanese post office
-"🏤", // 1F3E4 ; fully-qualified # 🏤 post office
-"🏥", // 1F3E5 ; fully-qualified # 🏥 hospital
-"🏦", // 1F3E6 ; fully-qualified # 🏦 bank
-"🏨", // 1F3E8 ; fully-qualified # 🏨 hotel
-"🏩", // 1F3E9 ; fully-qualified # 🏩 love hotel
-"🏪", // 1F3EA ; fully-qualified # 🏪 convenience store
-"🏫", // 1F3EB ; fully-qualified # 🏫 school
-"🏬", // 1F3EC ; fully-qualified # 🏬 department store
-"🏭", // 1F3ED ; fully-qualified # 🏭 factory
-"🏯", // 1F3EF ; fully-qualified # 🏯 Japanese castle
-"🏰", // 1F3F0 ; fully-qualified # 🏰 castle
-"💒", // 1F492 ; fully-qualified # 💒 wedding
-"🗼", // 1F5FC ; fully-qualified # 🗼 Tokyo tower
-"🗽", // 1F5FD ; fully-qualified # 🗽 Statue of Liberty
-"⛪", // 26EA ; fully-qualified # ⛪ church
-"🕌", // 1F54C ; fully-qualified # 🕌 mosque
-"🛕", // 1F6D5 ; fully-qualified # 🛕 hindu temple
-"🕍", // 1F54D ; fully-qualified # 🕍 synagogue
-"🕋", // 1F54B ; fully-qualified # 🕋 kaaba
-"⛲", // 26F2 ; fully-qualified # ⛲ fountain
-"⛺", // 26FA ; fully-qualified # ⛺ tent
-"🌁", // 1F301 ; fully-qualified # 🌁 foggy
-"🌃", // 1F303 ; fully-qualified # 🌃 night with stars
-"🌄", // 1F304 ; fully-qualified # 🌄 sunrise over mountains
-"🌅", // 1F305 ; fully-qualified # 🌅 sunrise
-"🌆", // 1F306 ; fully-qualified # 🌆 cityscape at dusk
-"🌇", // 1F307 ; fully-qualified # 🌇 sunset
-"🌉", // 1F309 ; fully-qualified # 🌉 bridge at night
-"🎠", // 1F3A0 ; fully-qualified # 🎠 carousel horse
-"🎡", // 1F3A1 ; fully-qualified # 🎡 ferris wheel
-"🎢", // 1F3A2 ; fully-qualified # 🎢 roller coaster
-"💈", // 1F488 ; fully-qualified # 💈 barber pole
-"🎪", // 1F3AA ; fully-qualified # 🎪 circus tent
-"🚂", // 1F682 ; fully-qualified # 🚂 locomotive
-"🚃", // 1F683 ; fully-qualified # 🚃 railway car
-"🚄", // 1F684 ; fully-qualified # 🚄 high-speed train
-"🚅", // 1F685 ; fully-qualified # 🚅 bullet train
-"🚆", // 1F686 ; fully-qualified # 🚆 train
-"🚇", // 1F687 ; fully-qualified # 🚇 metro
-"🚈", // 1F688 ; fully-qualified # 🚈 light rail
-"🚉", // 1F689 ; fully-qualified # 🚉 station
-"🚊", // 1F68A ; fully-qualified # 🚊 tram
-"🚝", // 1F69D ; fully-qualified # 🚝 monorail
-"🚞", // 1F69E ; fully-qualified # 🚞 mountain railway
-"🚋", // 1F68B ; fully-qualified # 🚋 tram car
-"🚌", // 1F68C ; fully-qualified # 🚌 bus
-"🚍", // 1F68D ; fully-qualified # 🚍 oncoming bus
-"🚎", // 1F68E ; fully-qualified # 🚎 trolleybus
-"🚐", // 1F690 ; fully-qualified # 🚐 minibus
-"🚑", // 1F691 ; fully-qualified # 🚑 ambulance
-"🚒", // 1F692 ; fully-qualified # 🚒 fire engine
-"🚓", // 1F693 ; fully-qualified # 🚓 police car
-"🚔", // 1F694 ; fully-qualified # 🚔 oncoming police car
-"🚕", // 1F695 ; fully-qualified # 🚕 taxi
-"🚖", // 1F696 ; fully-qualified # 🚖 oncoming taxi
-"🚗", // 1F697 ; fully-qualified # 🚗 automobile
-"🚘", // 1F698 ; fully-qualified # 🚘 oncoming automobile
-"🚙", // 1F699 ; fully-qualified # 🚙 sport utility vehicle
-"🚚", // 1F69A ; fully-qualified # 🚚 delivery truck
-"🚛", // 1F69B ; fully-qualified # 🚛 articulated lorry
-"🚜", // 1F69C ; fully-qualified # 🚜 tractor
-"🛵", // 1F6F5 ; fully-qualified # 🛵 motor scooter
-"🦽", // 1F9BD ; fully-qualified # 🦽 manual wheelchair
-"🦼", // 1F9BC ; fully-qualified # 🦼 motorized wheelchair
-"🛺", // 1F6FA ; fully-qualified # 🛺 auto rickshaw
-"🚲", // 1F6B2 ; fully-qualified # 🚲 bicycle
-"🛴", // 1F6F4 ; fully-qualified # 🛴 kick scooter
-"🛹", // 1F6F9 ; fully-qualified # 🛹 skateboard
-"🚏", // 1F68F ; fully-qualified # 🚏 bus stop
-"⛽", // 26FD ; fully-qualified # ⛽ fuel pump
-"🚨", // 1F6A8 ; fully-qualified # 🚨 police car light
-"🚥", // 1F6A5 ; fully-qualified # 🚥 horizontal traffic light
-"🚦", // 1F6A6 ; fully-qualified # 🚦 vertical traffic light
-"🛑", // 1F6D1 ; fully-qualified # 🛑 stop sign
-"🚧", // 1F6A7 ; fully-qualified # 🚧 construction
-"⚓", // 2693 ; fully-qualified # ⚓ anchor
-"⛵", // 26F5 ; fully-qualified # ⛵ sailboat
-"🛶", // 1F6F6 ; fully-qualified # 🛶 canoe
-"🚤", // 1F6A4 ; fully-qualified # 🚤 speedboat
-"🚢", // 1F6A2 ; fully-qualified # 🚢 ship
-"🛫", // 1F6EB ; fully-qualified # 🛫 airplane departure
-"🛬", // 1F6EC ; fully-qualified # 🛬 airplane arrival
-"🪂", // 1FA82 ; fully-qualified # 🪂 parachute
-"💺", // 1F4BA ; fully-qualified # 💺 seat
-"🚁", // 1F681 ; fully-qualified # 🚁 helicopter
-"🚟", // 1F69F ; fully-qualified # 🚟 suspension railway
-"🚠", // 1F6A0 ; fully-qualified # 🚠 mountain cableway
-"🚡", // 1F6A1 ; fully-qualified # 🚡 aerial tramway
-"🚀", // 1F680 ; fully-qualified # 🚀 rocket
-"🛸", // 1F6F8 ; fully-qualified # 🛸 flying saucer
-"🧳", // 1F9F3 ; fully-qualified # 🧳 luggage
-"⌛", // 231B ; fully-qualified # ⌛ hourglass done
-"⏳", // 23F3 ; fully-qualified # ⏳ hourglass not done
-"⌚", // 231A ; fully-qualified # ⌚ watch
-"⏰", // 23F0 ; fully-qualified # ⏰ alarm clock
-"🕛", // 1F55B ; fully-qualified # 🕛 twelve o’clock
-"🕧", // 1F567 ; fully-qualified # 🕧 twelve-thirty
-"🕐", // 1F550 ; fully-qualified # 🕐 one o’clock
-"🕜", // 1F55C ; fully-qualified # 🕜 one-thirty
-"🕑", // 1F551 ; fully-qualified # 🕑 two o’clock
-"🕝", // 1F55D ; fully-qualified # 🕝 two-thirty
-"🕒", // 1F552 ; fully-qualified # 🕒 three o’clock
-"🕞", // 1F55E ; fully-qualified # 🕞 three-thirty
-"🕓", // 1F553 ; fully-qualified # 🕓 four o’clock
-"🕟", // 1F55F ; fully-qualified # 🕟 four-thirty
-"🕔", // 1F554 ; fully-qualified # 🕔 five o’clock
-"🕠", // 1F560 ; fully-qualified # 🕠 five-thirty
-"🕕", // 1F555 ; fully-qualified # 🕕 six o’clock
-"🕡", // 1F561 ; fully-qualified # 🕡 six-thirty
-"🕖", // 1F556 ; fully-qualified # 🕖 seven o’clock
-"🕢", // 1F562 ; fully-qualified # 🕢 seven-thirty
-"🕗", // 1F557 ; fully-qualified # 🕗 eight o’clock
-"🕣", // 1F563 ; fully-qualified # 🕣 eight-thirty
-"🕘", // 1F558 ; fully-qualified # 🕘 nine o’clock
-"🕤", // 1F564 ; fully-qualified # 🕤 nine-thirty
-"🕙", // 1F559 ; fully-qualified # 🕙 ten o’clock
-"🕥", // 1F565 ; fully-qualified # 🕥 ten-thirty
-"🕚", // 1F55A ; fully-qualified # 🕚 eleven o’clock
-"🕦", // 1F566 ; fully-qualified # 🕦 eleven-thirty
-"🌑", // 1F311 ; fully-qualified # 🌑 new moon
-"🌒", // 1F312 ; fully-qualified # 🌒 waxing crescent moon
-"🌓", // 1F313 ; fully-qualified # 🌓 first quarter moon
-"🌔", // 1F314 ; fully-qualified # 🌔 waxing gibbous moon
-"🌕", // 1F315 ; fully-qualified # 🌕 full moon
-"🌖", // 1F316 ; fully-qualified # 🌖 waning gibbous moon
-"🌗", // 1F317 ; fully-qualified # 🌗 last quarter moon
-"🌘", // 1F318 ; fully-qualified # 🌘 waning crescent moon
-"🌙", // 1F319 ; fully-qualified # 🌙 crescent moon
-"🌚", // 1F31A ; fully-qualified # 🌚 new moon face
-"🌛", // 1F31B ; fully-qualified # 🌛 first quarter moon face
-"🌜", // 1F31C ; fully-qualified # 🌜 last quarter moon face
-"🌝", // 1F31D ; fully-qualified # 🌝 full moon face
-"🌞", // 1F31E ; fully-qualified # 🌞 sun with face
-"🪐", // 1FA90 ; fully-qualified # 🪐 ringed planet
-"⭐", // 2B50 ; fully-qualified # ⭐ star
-"🌟", // 1F31F ; fully-qualified # 🌟 glowing star
-"🌠", // 1F320 ; fully-qualified # 🌠 shooting star
-"🌌", // 1F30C ; fully-qualified # 🌌 milky way
-"⛅", // 26C5 ; fully-qualified # ⛅ sun behind cloud
-"🌀", // 1F300 ; fully-qualified # 🌀 cyclone
-"🌈", // 1F308 ; fully-qualified # 🌈 rainbow
-"🌂", // 1F302 ; fully-qualified # 🌂 closed umbrella
-"☔", // 2614 ; fully-qualified # ☔ umbrella with rain drops
-"⚡", // 26A1 ; fully-qualified # ⚡ high voltage
-"⛄", // 26C4 ; fully-qualified # ⛄ snowman without snow
-"🔥", // 1F525 ; fully-qualified # 🔥 fire
-"💧", // 1F4A7 ; fully-qualified # 💧 droplet
-"🌊", // 1F30A ; fully-qualified # 🌊 water wave
-},
-{ // # group: Activities
-"🎃", // 1F383 ; fully-qualified # 🎃 jack-o-lantern
-"🎄", // 1F384 ; fully-qualified # 🎄 Christmas tree
-"🎆", // 1F386 ; fully-qualified # 🎆 fireworks
-"🎇", // 1F387 ; fully-qualified # 🎇 sparkler
-"🧨", // 1F9E8 ; fully-qualified # 🧨 firecracker
-"✨", // 2728 ; fully-qualified # ✨ sparkles
-"🎈", // 1F388 ; fully-qualified # 🎈 balloon
-"🎉", // 1F389 ; fully-qualified # 🎉 party popper
-"🎊", // 1F38A ; fully-qualified # 🎊 confetti ball
-"🎋", // 1F38B ; fully-qualified # 🎋 tanabata tree
-"🎍", // 1F38D ; fully-qualified # 🎍 pine decoration
-"🎎", // 1F38E ; fully-qualified # 🎎 Japanese dolls
-"🎏", // 1F38F ; fully-qualified # 🎏 carp streamer
-"🎐", // 1F390 ; fully-qualified # 🎐 wind chime
-"🎑", // 1F391 ; fully-qualified # 🎑 moon viewing ceremony
-"🧧", // 1F9E7 ; fully-qualified # 🧧 red envelope
-"🎀", // 1F380 ; fully-qualified # 🎀 ribbon
-"🎁", // 1F381 ; fully-qualified # 🎁 wrapped gift
-"🎫", // 1F3AB ; fully-qualified # 🎫 ticket
-"🏆", // 1F3C6 ; fully-qualified # 🏆 trophy
-"🏅", // 1F3C5 ; fully-qualified # 🏅 sports medal
-"🥇", // 1F947 ; fully-qualified # 🥇 1st place medal
-"🥈", // 1F948 ; fully-qualified # 🥈 2nd place medal
-"🥉", // 1F949 ; fully-qualified # 🥉 3rd place medal
-"⚽", // 26BD ; fully-qualified # ⚽ soccer ball
-"⚾", // 26BE ; fully-qualified # ⚾ baseball
-"🥎", // 1F94E ; fully-qualified # 🥎 softball
-"🏀", // 1F3C0 ; fully-qualified # 🏀 basketball
-"🏐", // 1F3D0 ; fully-qualified # 🏐 volleyball
-"🏈", // 1F3C8 ; fully-qualified # 🏈 american football
-"🏉", // 1F3C9 ; fully-qualified # 🏉 rugby football
-"🎾", // 1F3BE ; fully-qualified # 🎾 tennis
-"🥏", // 1F94F ; fully-qualified # 🥏 flying disc
-"🎳", // 1F3B3 ; fully-qualified # 🎳 bowling
-"🏏", // 1F3CF ; fully-qualified # 🏏 cricket game
-"🏑", // 1F3D1 ; fully-qualified # 🏑 field hockey
-"🏒", // 1F3D2 ; fully-qualified # 🏒 ice hockey
-"🥍", // 1F94D ; fully-qualified # 🥍 lacrosse
-"🏓", // 1F3D3 ; fully-qualified # 🏓 ping pong
-"🏸", // 1F3F8 ; fully-qualified # 🏸 badminton
-"🥊", // 1F94A ; fully-qualified # 🥊 boxing glove
-"🥋", // 1F94B ; fully-qualified # 🥋 martial arts uniform
-"🥅", // 1F945 ; fully-qualified # 🥅 goal net
-"⛳", // 26F3 ; fully-qualified # ⛳ flag in hole
-"🎣", // 1F3A3 ; fully-qualified # 🎣 fishing pole
-"🤿", // 1F93F ; fully-qualified # 🤿 diving mask
-"🎽", // 1F3BD ; fully-qualified # 🎽 running shirt
-"🎿", // 1F3BF ; fully-qualified # 🎿 skis
-"🛷", // 1F6F7 ; fully-qualified # 🛷 sled
-"🥌", // 1F94C ; fully-qualified # 🥌 curling stone
-"🎯", // 1F3AF ; fully-qualified # 🎯 direct hit
-"🪀", // 1FA80 ; fully-qualified # 🪀 yo-yo
-"🪁", // 1FA81 ; fully-qualified # 🪁 kite
-"🎱", // 1F3B1 ; fully-qualified # 🎱 pool 8 ball
-"🔮", // 1F52E ; fully-qualified # 🔮 crystal ball
-"🧿", // 1F9FF ; fully-qualified # 🧿 nazar amulet
-"🎮", // 1F3AE ; fully-qualified # 🎮 video game
-"🎰", // 1F3B0 ; fully-qualified # 🎰 slot machine
-"🎲", // 1F3B2 ; fully-qualified # 🎲 game die
-"🧩", // 1F9E9 ; fully-qualified # 🧩 puzzle piece
-"🧸", // 1F9F8 ; fully-qualified # 🧸 teddy bear
-"🃏", // 1F0CF ; fully-qualified # 🃏 joker
-"🀄", // 1F004 ; fully-qualified # 🀄 mahjong red dragon
-"🎴", // 1F3B4 ; fully-qualified # 🎴 flower playing cards
-"🎭", // 1F3AD ; fully-qualified # 🎭 performing arts
-"🎨", // 1F3A8 ; fully-qualified # 🎨 artist palette
-"🧵", // 1F9F5 ; fully-qualified # 🧵 thread
-"🧶", // 1F9F6 ; fully-qualified # 🧶 yarn
-},
-{ // # group: Objects
-"👓", // 1F453 ; fully-qualified # 👓 glasses
-"🥽", // 1F97D ; fully-qualified # 🥽 goggles
-"🥼", // 1F97C ; fully-qualified # 🥼 lab coat
-"🦺", // 1F9BA ; fully-qualified # 🦺 safety vest
-"👔", // 1F454 ; fully-qualified # 👔 necktie
-"👕", // 1F455 ; fully-qualified # 👕 t-shirt
-"👖", // 1F456 ; fully-qualified # 👖 jeans
-"🧣", // 1F9E3 ; fully-qualified # 🧣 scarf
-"🧤", // 1F9E4 ; fully-qualified # 🧤 gloves
-"🧥", // 1F9E5 ; fully-qualified # 🧥 coat
-"🧦", // 1F9E6 ; fully-qualified # 🧦 socks
-"👗", // 1F457 ; fully-qualified # 👗 dress
-"👘", // 1F458 ; fully-qualified # 👘 kimono
-"🥻", // 1F97B ; fully-qualified # 🥻 sari
-"🩱", // 1FA71 ; fully-qualified # 🩱 one-piece swimsuit
-"🩲", // 1FA72 ; fully-qualified # 🩲 swim brief
-"🩳", // 1FA73 ; fully-qualified # 🩳 shorts
-"👙", // 1F459 ; fully-qualified # 👙 bikini
-"👚", // 1F45A ; fully-qualified # 👚 woman’s clothes
-"👛", // 1F45B ; fully-qualified # 👛 purse
-"👜", // 1F45C ; fully-qualified # 👜 handbag
-"👝", // 1F45D ; fully-qualified # 👝 clutch bag
-"🎒", // 1F392 ; fully-qualified # 🎒 backpack
-"👞", // 1F45E ; fully-qualified # 👞 man’s shoe
-"👟", // 1F45F ; fully-qualified # 👟 running shoe
-"🥾", // 1F97E ; fully-qualified # 🥾 hiking boot
-"🥿", // 1F97F ; fully-qualified # 🥿 flat shoe
-"👠", // 1F460 ; fully-qualified # 👠 high-heeled shoe
-"👡", // 1F461 ; fully-qualified # 👡 woman’s sandal
-"🩰", // 1FA70 ; fully-qualified # 🩰 ballet shoes
-"👢", // 1F462 ; fully-qualified # 👢 woman’s boot
-"👑", // 1F451 ; fully-qualified # 👑 crown
-"👒", // 1F452 ; fully-qualified # 👒 woman’s hat
-"🎩", // 1F3A9 ; fully-qualified # 🎩 top hat
-"🎓", // 1F393 ; fully-qualified # 🎓 graduation cap
-"🧢", // 1F9E2 ; fully-qualified # 🧢 billed cap
-"📿", // 1F4FF ; fully-qualified # 📿 prayer beads
-"💄", // 1F484 ; fully-qualified # 💄 lipstick
-"💍", // 1F48D ; fully-qualified # 💍 ring
-"💎", // 1F48E ; fully-qualified # 💎 gem stone
-"🔇", // 1F507 ; fully-qualified # 🔇 muted speaker
-"🔈", // 1F508 ; fully-qualified # 🔈 speaker low volume
-"🔉", // 1F509 ; fully-qualified # 🔉 speaker medium volume
-"🔊", // 1F50A ; fully-qualified # 🔊 speaker high volume
-"📢", // 1F4E2 ; fully-qualified # 📢 loudspeaker
-"📣", // 1F4E3 ; fully-qualified # 📣 megaphone
-"📯", // 1F4EF ; fully-qualified # 📯 postal horn
-"🔔", // 1F514 ; fully-qualified # 🔔 bell
-"🔕", // 1F515 ; fully-qualified # 🔕 bell with slash
-"🎼", // 1F3BC ; fully-qualified # 🎼 musical score
-"🎵", // 1F3B5 ; fully-qualified # 🎵 musical note
-"🎶", // 1F3B6 ; fully-qualified # 🎶 musical notes
-"🎤", // 1F3A4 ; fully-qualified # 🎤 microphone
-"🎧", // 1F3A7 ; fully-qualified # 🎧 headphone
-"📻", // 1F4FB ; fully-qualified # 📻 radio
-"🎷", // 1F3B7 ; fully-qualified # 🎷 saxophone
-"🎸", // 1F3B8 ; fully-qualified # 🎸 guitar
-"🎹", // 1F3B9 ; fully-qualified # 🎹 musical keyboard
-"🎺", // 1F3BA ; fully-qualified # 🎺 trumpet
-"🎻", // 1F3BB ; fully-qualified # 🎻 violin
-"🪕", // 1FA95 ; fully-qualified # 🪕 banjo
-"🥁", // 1F941 ; fully-qualified # 🥁 drum
-"📱", // 1F4F1 ; fully-qualified # 📱 mobile phone
-"📲", // 1F4F2 ; fully-qualified # 📲 mobile phone with arrow
-"📞", // 1F4DE ; fully-qualified # 📞 telephone receiver
-"📟", // 1F4DF ; fully-qualified # 📟 pager
-"📠", // 1F4E0 ; fully-qualified # 📠 fax machine
-"🔋", // 1F50B ; fully-qualified # 🔋 battery
-"🔌", // 1F50C ; fully-qualified # 🔌 electric plug
-"💻", // 1F4BB ; fully-qualified # 💻 laptop computer
-"💽", // 1F4BD ; fully-qualified # 💽 computer disk
-"💾", // 1F4BE ; fully-qualified # 💾 floppy disk
-"💿", // 1F4BF ; fully-qualified # 💿 optical disk
-"📀", // 1F4C0 ; fully-qualified # 📀 dvd
-"🧮", // 1F9EE ; fully-qualified # 🧮 abacus
-"🎥", // 1F3A5 ; fully-qualified # 🎥 movie camera
-"🎬", // 1F3AC ; fully-qualified # 🎬 clapper board
-"📺", // 1F4FA ; fully-qualified # 📺 television
-"📷", // 1F4F7 ; fully-qualified # 📷 camera
-"📸", // 1F4F8 ; fully-qualified # 📸 camera with flash
-"📹", // 1F4F9 ; fully-qualified # 📹 video camera
-"📼", // 1F4FC ; fully-qualified # 📼 videocassette
-"🔍", // 1F50D ; fully-qualified # 🔍 magnifying glass tilted left
-"🔎", // 1F50E ; fully-qualified # 🔎 magnifying glass tilted right
-"💡", // 1F4A1 ; fully-qualified # 💡 light bulb
-"🔦", // 1F526 ; fully-qualified # 🔦 flashlight
-"🏮", // 1F3EE ; fully-qualified # 🏮 red paper lantern
-"🪔", // 1FA94 ; fully-qualified # 🪔 diya lamp
-"📔", // 1F4D4 ; fully-qualified # 📔 notebook with decorative cover
-"📕", // 1F4D5 ; fully-qualified # 📕 closed book
-"📖", // 1F4D6 ; fully-qualified # 📖 open book
-"📗", // 1F4D7 ; fully-qualified # 📗 green book
-"📘", // 1F4D8 ; fully-qualified # 📘 blue book
-"📙", // 1F4D9 ; fully-qualified # 📙 orange book
-"📚", // 1F4DA ; fully-qualified # 📚 books
-"📓", // 1F4D3 ; fully-qualified # 📓 notebook
-"📒", // 1F4D2 ; fully-qualified # 📒 ledger
-"📃", // 1F4C3 ; fully-qualified # 📃 page with curl
-"📜", // 1F4DC ; fully-qualified # 📜 scroll
-"📄", // 1F4C4 ; fully-qualified # 📄 page facing up
-"📰", // 1F4F0 ; fully-qualified # 📰 newspaper
-"📑", // 1F4D1 ; fully-qualified # 📑 bookmark tabs
-"🔖", // 1F516 ; fully-qualified # 🔖 bookmark
-"💰", // 1F4B0 ; fully-qualified # 💰 money bag
-"💴", // 1F4B4 ; fully-qualified # 💴 yen banknote
-"💵", // 1F4B5 ; fully-qualified # 💵 dollar banknote
-"💶", // 1F4B6 ; fully-qualified # 💶 euro banknote
-"💷", // 1F4B7 ; fully-qualified # 💷 pound banknote
-"💸", // 1F4B8 ; fully-qualified # 💸 money with wings
-"💳", // 1F4B3 ; fully-qualified # 💳 credit card
-"🧾", // 1F9FE ; fully-qualified # 🧾 receipt
-"💹", // 1F4B9 ; fully-qualified # 💹 chart increasing with yen
-"💱", // 1F4B1 ; fully-qualified # 💱 currency exchange
-"💲", // 1F4B2 ; fully-qualified # 💲 heavy dollar sign
-"📧", // 1F4E7 ; fully-qualified # 📧 e-mail
-"📨", // 1F4E8 ; fully-qualified # 📨 incoming envelope
-"📩", // 1F4E9 ; fully-qualified # 📩 envelope with arrow
-"📤", // 1F4E4 ; fully-qualified # 📤 outbox tray
-"📥", // 1F4E5 ; fully-qualified # 📥 inbox tray
-"📦", // 1F4E6 ; fully-qualified # 📦 package
-"📫", // 1F4EB ; fully-qualified # 📫 closed mailbox with raised flag
-"📪", // 1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag
-"📬", // 1F4EC ; fully-qualified # 📬 open mailbox with raised flag
-"📭", // 1F4ED ; fully-qualified # 📭 open mailbox with lowered flag
-"📮", // 1F4EE ; fully-qualified # 📮 postbox
-"📝", // 1F4DD ; fully-qualified # 📝 memo
-"💼", // 1F4BC ; fully-qualified # 💼 briefcase
-"📁", // 1F4C1 ; fully-qualified # 📁 file folder
-"📂", // 1F4C2 ; fully-qualified # 📂 open file folder
-"📅", // 1F4C5 ; fully-qualified # 📅 calendar
-"📆", // 1F4C6 ; fully-qualified # 📆 tear-off calendar
-"📇", // 1F4C7 ; fully-qualified # 📇 card index
-"📈", // 1F4C8 ; fully-qualified # 📈 chart increasing
-"📉", // 1F4C9 ; fully-qualified # 📉 chart decreasing
-"📊", // 1F4CA ; fully-qualified # 📊 bar chart
-"📋", // 1F4CB ; fully-qualified # 📋 clipboard
-"📌", // 1F4CC ; fully-qualified # 📌 pushpin
-"📍", // 1F4CD ; fully-qualified # 📍 round pushpin
-"📎", // 1F4CE ; fully-qualified # 📎 paperclip
-"📏", // 1F4CF ; fully-qualified # 📏 straight ruler
-"📐", // 1F4D0 ; fully-qualified # 📐 triangular ruler
-"🔒", // 1F512 ; fully-qualified # 🔒 locked
-"🔓", // 1F513 ; fully-qualified # 🔓 unlocked
-"🔏", // 1F50F ; fully-qualified # 🔏 locked with pen
-"🔐", // 1F510 ; fully-qualified # 🔐 locked with key
-"🔑", // 1F511 ; fully-qualified # 🔑 key
-"🔨", // 1F528 ; fully-qualified # 🔨 hammer
-"🪓", // 1FA93 ; fully-qualified # 🪓 axe
-"🔫", // 1F52B ; fully-qualified # 🔫 pistol
-"🏹", // 1F3F9 ; fully-qualified # 🏹 bow and arrow
-"🔧", // 1F527 ; fully-qualified # 🔧 wrench
-"🔩", // 1F529 ; fully-qualified # 🔩 nut and bolt
-"🦯", // 1F9AF ; fully-qualified # 🦯 probing cane
-"🔗", // 1F517 ; fully-qualified # 🔗 link
-"🧰", // 1F9F0 ; fully-qualified # 🧰 toolbox
-"🧲", // 1F9F2 ; fully-qualified # 🧲 magnet
-"🧪", // 1F9EA ; fully-qualified # 🧪 test tube
-"🧫", // 1F9EB ; fully-qualified # 🧫 petri dish
-"🧬", // 1F9EC ; fully-qualified # 🧬 dna
-"🔬", // 1F52C ; fully-qualified # 🔬 microscope
-"🔭", // 1F52D ; fully-qualified # 🔭 telescope
-"📡", // 1F4E1 ; fully-qualified # 📡 satellite antenna
-"💉", // 1F489 ; fully-qualified # 💉 syringe
-"🩸", // 1FA78 ; fully-qualified # 🩸 drop of blood
-"💊", // 1F48A ; fully-qualified # 💊 pill
-"🩹", // 1FA79 ; fully-qualified # 🩹 adhesive bandage
-"🩺", // 1FA7A ; fully-qualified # 🩺 stethoscope
-"🚪", // 1F6AA ; fully-qualified # 🚪 door
-"🪑", // 1FA91 ; fully-qualified # 🪑 chair
-"🚽", // 1F6BD ; fully-qualified # 🚽 toilet
-"🚿", // 1F6BF ; fully-qualified # 🚿 shower
-"🛁", // 1F6C1 ; fully-qualified # 🛁 bathtub
-"🪒", // 1FA92 ; fully-qualified # 🪒 razor
-"🧴", // 1F9F4 ; fully-qualified # 🧴 lotion bottle
-"🧷", // 1F9F7 ; fully-qualified # 🧷 safety pin
-"🧹", // 1F9F9 ; fully-qualified # 🧹 broom
-"🧺", // 1F9FA ; fully-qualified # 🧺 basket
-"🧻", // 1F9FB ; fully-qualified # 🧻 roll of paper
-"🧼", // 1F9FC ; fully-qualified # 🧼 soap
-"🧽", // 1F9FD ; fully-qualified # 🧽 sponge
-"🧯", // 1F9EF ; fully-qualified # 🧯 fire extinguisher
-"🛒", // 1F6D2 ; fully-qualified # 🛒 shopping cart
-"🚬", // 1F6AC ; fully-qualified # 🚬 cigarette
-"🗿", // 1F5FF ; fully-qualified # 🗿 moai
-},
-{ // # group: Symbols
-"🏧", // 1F3E7 ; fully-qualified # 🏧 ATM sign
-"🚮", // 1F6AE ; fully-qualified # 🚮 litter in bin sign
-"🚰", // 1F6B0 ; fully-qualified # 🚰 potable water
-"♿", // 267F ; fully-qualified # ♿ wheelchair symbol
-"🚹", // 1F6B9 ; fully-qualified # 🚹 men’s room
-"🚺", // 1F6BA ; fully-qualified # 🚺 women’s room
-"🚻", // 1F6BB ; fully-qualified # 🚻 restroom
-"🚼", // 1F6BC ; fully-qualified # 🚼 baby symbol
-"🚾", // 1F6BE ; fully-qualified # 🚾 water closet
-"🛂", // 1F6C2 ; fully-qualified # 🛂 passport control
-"🛃", // 1F6C3 ; fully-qualified # 🛃 customs
-"🛄", // 1F6C4 ; fully-qualified # 🛄 baggage claim
-"🛅", // 1F6C5 ; fully-qualified # 🛅 left luggage
-"🚸", // 1F6B8 ; fully-qualified # 🚸 children crossing
-"⛔", // 26D4 ; fully-qualified # ⛔ no entry
-"🚫", // 1F6AB ; fully-qualified # 🚫 prohibited
-"🚳", // 1F6B3 ; fully-qualified # 🚳 no bicycles
-"🚭", // 1F6AD ; fully-qualified # 🚭 no smoking
-"🚯", // 1F6AF ; fully-qualified # 🚯 no littering
-"🚱", // 1F6B1 ; fully-qualified # 🚱 non-potable water
-"🚷", // 1F6B7 ; fully-qualified # 🚷 no pedestrians
-"📵", // 1F4F5 ; fully-qualified # 📵 no mobile phones
-"🔞", // 1F51E ; fully-qualified # 🔞 no one under eighteen
-"🔃", // 1F503 ; fully-qualified # 🔃 clockwise vertical arrows
-"🔄", // 1F504 ; fully-qualified # 🔄 counterclockwise arrows button
-"🔙", // 1F519 ; fully-qualified # 🔙 BACK arrow
-"🔚", // 1F51A ; fully-qualified # 🔚 END arrow
-"🔛", // 1F51B ; fully-qualified # 🔛 ON! arrow
-"🔜", // 1F51C ; fully-qualified # 🔜 SOON arrow
-"🔝", // 1F51D ; fully-qualified # 🔝 TOP arrow
-"🛐", // 1F6D0 ; fully-qualified # 🛐 place of worship
-"🕎", // 1F54E ; fully-qualified # 🕎 menorah
-"🔯", // 1F52F ; fully-qualified # 🔯 dotted six-pointed star
-"♈", // 2648 ; fully-qualified # ♈ Aries
-"♉", // 2649 ; fully-qualified # ♉ Taurus
-"♊", // 264A ; fully-qualified # ♊ Gemini
-"♋", // 264B ; fully-qualified # ♋ Cancer
-"♌", // 264C ; fully-qualified # ♌ Leo
-"♍", // 264D ; fully-qualified # ♍ Virgo
-"♎", // 264E ; fully-qualified # ♎ Libra
-"♏", // 264F ; fully-qualified # ♏ Scorpio
-"♐", // 2650 ; fully-qualified # ♐ Sagittarius
-"♑", // 2651 ; fully-qualified # ♑ Capricorn
-"♒", // 2652 ; fully-qualified # ♒ Aquarius
-"♓", // 2653 ; fully-qualified # ♓ Pisces
-"⛎", // 26CE ; fully-qualified # ⛎ Ophiuchus
-"🔀", // 1F500 ; fully-qualified # 🔀 shuffle tracks button
-"🔁", // 1F501 ; fully-qualified # 🔁 repeat button
-"🔂", // 1F502 ; fully-qualified # 🔂 repeat single button
-"⏩", // 23E9 ; fully-qualified # ⏩ fast-forward button
-"⏪", // 23EA ; fully-qualified # ⏪ fast reverse button
-"🔼", // 1F53C ; fully-qualified # 🔼 upwards button
-"⏫", // 23EB ; fully-qualified # ⏫ fast up button
-"🔽", // 1F53D ; fully-qualified # 🔽 downwards button
-"⏬", // 23EC ; fully-qualified # ⏬ fast down button
-"🎦", // 1F3A6 ; fully-qualified # 🎦 cinema
-"🔅", // 1F505 ; fully-qualified # 🔅 dim button
-"🔆", // 1F506 ; fully-qualified # 🔆 bright button
-"📶", // 1F4F6 ; fully-qualified # 📶 antenna bars
-"📳", // 1F4F3 ; fully-qualified # 📳 vibration mode
-"📴", // 1F4F4 ; fully-qualified # 📴 mobile phone off
-"🔱", // 1F531 ; fully-qualified # 🔱 trident emblem
-"📛", // 1F4DB ; fully-qualified # 📛 name badge
-"🔰", // 1F530 ; fully-qualified # 🔰 Japanese symbol for beginner
-"⭕", // 2B55 ; fully-qualified # ⭕ hollow red circle
-"✅", // 2705 ; fully-qualified # ✅ check mark button
-"❌", // 274C ; fully-qualified # ❌ cross mark
-"❎", // 274E ; fully-qualified # ❎ cross mark button
-"➕", // 2795 ; fully-qualified # ➕ plus sign
-"➖", // 2796 ; fully-qualified # ➖ minus sign
-"➗", // 2797 ; fully-qualified # ➗ division sign
-"➰", // 27B0 ; fully-qualified # ➰ curly loop
-"➿", // 27BF ; fully-qualified # ➿ double curly loop
-"❓", // 2753 ; fully-qualified # ❓ question mark
-"❔", // 2754 ; fully-qualified # ❔ white question mark
-"❕", // 2755 ; fully-qualified # ❕ white exclamation mark
-"❗", // 2757 ; fully-qualified # ❗ exclamation mark
-"🔟", // 1F51F ; fully-qualified # 🔟 keycap: 10
-"🔠", // 1F520 ; fully-qualified # 🔠 input latin uppercase
-"🔡", // 1F521 ; fully-qualified # 🔡 input latin lowercase
-"🔢", // 1F522 ; fully-qualified # 🔢 input numbers
-"🔣", // 1F523 ; fully-qualified # 🔣 input symbols
-"🔤", // 1F524 ; fully-qualified # 🔤 input latin letters
-"🆎", // 1F18E ; fully-qualified # 🆎 AB button (blood type)
-"🆑", // 1F191 ; fully-qualified # 🆑 CL button
-"🆒", // 1F192 ; fully-qualified # 🆒 COOL button
-"🆓", // 1F193 ; fully-qualified # 🆓 FREE button
-"🆔", // 1F194 ; fully-qualified # 🆔 ID button
-"🆕", // 1F195 ; fully-qualified # 🆕 NEW button
-"🆖", // 1F196 ; fully-qualified # 🆖 NG button
-"🆗", // 1F197 ; fully-qualified # 🆗 OK button
-"🆘", // 1F198 ; fully-qualified # 🆘 SOS button
-"🆙", // 1F199 ; fully-qualified # 🆙 UP! button
-"🆚", // 1F19A ; fully-qualified # 🆚 VS button
-"🈁", // 1F201 ; fully-qualified # 🈁 Japanese “here” button
-"🈶", // 1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button
-"🈯", // 1F22F ; fully-qualified # 🈯 Japanese “reserved” button
-"🉐", // 1F250 ; fully-qualified # 🉐 Japanese “bargain” button
-"🈹", // 1F239 ; fully-qualified # 🈹 Japanese “discount” button
-"🈚", // 1F21A ; fully-qualified # 🈚 Japanese “free of charge” button
-"🈲", // 1F232 ; fully-qualified # 🈲 Japanese “prohibited” button
-"🉑", // 1F251 ; fully-qualified # 🉑 Japanese “acceptable” button
-"🈸", // 1F238 ; fully-qualified # 🈸 Japanese “application” button
-"🈴", // 1F234 ; fully-qualified # 🈴 Japanese “passing grade” button
-"🈳", // 1F233 ; fully-qualified # 🈳 Japanese “vacancy” button
-"🈺", // 1F23A ; fully-qualified # 🈺 Japanese “open for business” button
-"🈵", // 1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button
-"🔴", // 1F534 ; fully-qualified # 🔴 red circle
-"🟠", // 1F7E0 ; fully-qualified # 🟠 orange circle
-"🟡", // 1F7E1 ; fully-qualified # 🟡 yellow circle
-"🟢", // 1F7E2 ; fully-qualified # 🟢 green circle
-"🔵", // 1F535 ; fully-qualified # 🔵 blue circle
-"🟣", // 1F7E3 ; fully-qualified # 🟣 purple circle
-"🟤", // 1F7E4 ; fully-qualified # 🟤 brown circle
-"⚫", // 26AB ; fully-qualified # ⚫ black circle
-"⚪", // 26AA ; fully-qualified # ⚪ white circle
-"🟥", // 1F7E5 ; fully-qualified # 🟥 red square
-"🟧", // 1F7E7 ; fully-qualified # 🟧 orange square
-"🟨", // 1F7E8 ; fully-qualified # 🟨 yellow square
-"🟩", // 1F7E9 ; fully-qualified # 🟩 green square
-"🟦", // 1F7E6 ; fully-qualified # 🟦 blue square
-"🟪", // 1F7EA ; fully-qualified # 🟪 purple square
-"🟫", // 1F7EB ; fully-qualified # 🟫 brown square
-"⬛", // 2B1B ; fully-qualified # ⬛ black large square
-"⬜", // 2B1C ; fully-qualified # ⬜ white large square
-"◾", // 25FE ; fully-qualified # ◾ black medium-small square
-"◽", // 25FD ; fully-qualified # ◽ white medium-small square
-"🔶", // 1F536 ; fully-qualified # 🔶 large orange diamond
-"🔷", // 1F537 ; fully-qualified # 🔷 large blue diamond
-"🔸", // 1F538 ; fully-qualified # 🔸 small orange diamond
-"🔹", // 1F539 ; fully-qualified # 🔹 small blue diamond
-"🔺", // 1F53A ; fully-qualified # 🔺 red triangle pointed up
-"🔻", // 1F53B ; fully-qualified # 🔻 red triangle pointed down
-"💠", // 1F4A0 ; fully-qualified # 💠 diamond with a dot
-"🔘", // 1F518 ; fully-qualified # 🔘 radio button
-"🔳", // 1F533 ; fully-qualified # 🔳 white square button
-"🔲", // 1F532 ; fully-qualified # 🔲 black square button
-},
-{ // # group: Flags
-"🏁", // 1F3C1 ; fully-qualified # 🏁 chequered flag
-"🚩", // 1F6A9 ; fully-qualified # 🚩 triangular flag
-"🎌", // 1F38C ; fully-qualified # 🎌 crossed flags
-"🏴", // 1F3F4 ; fully-qualified # 🏴 black flag
-}
- };
+public class Emojis {
+ public static final String[][] EMOJIS =
+ new String[][] {
+ { // # group: Smileys & Emotion
+ "😀", // 1F600 ; fully-qualified # 😀 grinning face
+ "😃", // 1F603 ; fully-qualified # 😃 grinning face with big eyes
+ "😄", // 1F604 ; fully-qualified # 😄 grinning face with smiling eyes
+ "😁", // 1F601 ; fully-qualified # 😁 beaming face with smiling eyes
+ "😆", // 1F606 ; fully-qualified # 😆 grinning squinting face
+ "😅", // 1F605 ; fully-qualified # 😅 grinning face with sweat
+ "🤣", // 1F923 ; fully-qualified # 🤣 rolling on the floor laughing
+ "😂", // 1F602 ; fully-qualified # 😂 face with tears of joy
+ "🙂", // 1F642 ; fully-qualified # 🙂 slightly smiling face
+ "🙃", // 1F643 ; fully-qualified # 🙃 upside-down face
+ "😉", // 1F609 ; fully-qualified # 😉 winking face
+ "😊", // 1F60A ; fully-qualified # 😊 smiling face with smiling eyes
+ "😇", // 1F607 ; fully-qualified # 😇 smiling face with halo
+ "🥰", // 1F970 ; fully-qualified # 🥰 smiling face with hearts
+ "😍", // 1F60D ; fully-qualified # 😍 smiling face with heart-eyes
+ "🤩", // 1F929 ; fully-qualified # 🤩 star-struck
+ "😘", // 1F618 ; fully-qualified # 😘 face blowing a kiss
+ "😗", // 1F617 ; fully-qualified # 😗 kissing face
+ "😚", // 1F61A ; fully-qualified # 😚 kissing face with closed eyes
+ "😙", // 1F619 ; fully-qualified # 😙 kissing face with smiling eyes
+ "😋", // 1F60B ; fully-qualified # 😋 face savoring food
+ "😛", // 1F61B ; fully-qualified # 😛 face with tongue
+ "😜", // 1F61C ; fully-qualified # 😜 winking face with tongue
+ "🤪", // 1F92A ; fully-qualified # 🤪 zany face
+ "😝", // 1F61D ; fully-qualified # 😝 squinting face with tongue
+ "🤑", // 1F911 ; fully-qualified # 🤑 money-mouth face
+ "🤗", // 1F917 ; fully-qualified # 🤗 hugging face
+ "🤭", // 1F92D ; fully-qualified # 🤭 face with hand over mouth
+ "🤫", // 1F92B ; fully-qualified # 🤫 shushing face
+ "🤔", // 1F914 ; fully-qualified # 🤔 thinking face
+ "🤐", // 1F910 ; fully-qualified # 🤐 zipper-mouth face
+ "🤨", // 1F928 ; fully-qualified # 🤨 face with raised eyebrow
+ "😐", // 1F610 ; fully-qualified # 😐 neutral face
+ "😑", // 1F611 ; fully-qualified # 😑 expressionless face
+ "😶", // 1F636 ; fully-qualified # 😶 face without mouth
+ "😏", // 1F60F ; fully-qualified # 😏 smirking face
+ "😒", // 1F612 ; fully-qualified # 😒 unamused face
+ "🙄", // 1F644 ; fully-qualified # 🙄 face with rolling eyes
+ "😬", // 1F62C ; fully-qualified # 😬 grimacing face
+ "🤥", // 1F925 ; fully-qualified # 🤥 lying face
+ "😌", // 1F60C ; fully-qualified # 😌 relieved face
+ "😔", // 1F614 ; fully-qualified # 😔 pensive face
+ "😪", // 1F62A ; fully-qualified # 😪 sleepy face
+ "🤤", // 1F924 ; fully-qualified # 🤤 drooling face
+ "😴", // 1F634 ; fully-qualified # 😴 sleeping face
+ "😷", // 1F637 ; fully-qualified # 😷 face with medical mask
+ "🤒", // 1F912 ; fully-qualified # 🤒 face with thermometer
+ "🤕", // 1F915 ; fully-qualified # 🤕 face with head-bandage
+ "🤢", // 1F922 ; fully-qualified # 🤢 nauseated face
+ "🤮", // 1F92E ; fully-qualified # 🤮 face vomiting
+ "🤧", // 1F927 ; fully-qualified # 🤧 sneezing face
+ "🥵", // 1F975 ; fully-qualified # 🥵 hot face
+ "🥶", // 1F976 ; fully-qualified # 🥶 cold face
+ "🥴", // 1F974 ; fully-qualified # 🥴 woozy face
+ "😵", // 1F635 ; fully-qualified # 😵 dizzy face
+ "🤯", // 1F92F ; fully-qualified # 🤯 exploding head
+ "🤠", // 1F920 ; fully-qualified # 🤠 cowboy hat face
+ "🥳", // 1F973 ; fully-qualified # 🥳 partying face
+ "😎", // 1F60E ; fully-qualified # 😎 smiling face with sunglasses
+ "🤓", // 1F913 ; fully-qualified # 🤓 nerd face
+ "🧐", // 1F9D0 ; fully-qualified # 🧐 face with monocle
+ "😕", // 1F615 ; fully-qualified # 😕 confused face
+ "😟", // 1F61F ; fully-qualified # 😟 worried face
+ "🙁", // 1F641 ; fully-qualified # 🙁 slightly frowning face
+ "😮", // 1F62E ; fully-qualified # 😮 face with open mouth
+ "😯", // 1F62F ; fully-qualified # 😯 hushed face
+ "😲", // 1F632 ; fully-qualified # 😲 astonished face
+ "😳", // 1F633 ; fully-qualified # 😳 flushed face
+ "🥺", // 1F97A ; fully-qualified # 🥺 pleading face
+ "😦", // 1F626 ; fully-qualified # 😦 frowning face with open mouth
+ "😧", // 1F627 ; fully-qualified # 😧 anguished face
+ "😨", // 1F628 ; fully-qualified # 😨 fearful face
+ "😰", // 1F630 ; fully-qualified # 😰 anxious face with sweat
+ "😥", // 1F625 ; fully-qualified # 😥 sad but relieved face
+ "😢", // 1F622 ; fully-qualified # 😢 crying face
+ "😭", // 1F62D ; fully-qualified # 😭 loudly crying face
+ "😱", // 1F631 ; fully-qualified # 😱 face screaming in fear
+ "😖", // 1F616 ; fully-qualified # 😖 confounded face
+ "😣", // 1F623 ; fully-qualified # 😣 persevering face
+ "😞", // 1F61E ; fully-qualified # 😞 disappointed face
+ "😓", // 1F613 ; fully-qualified # 😓 downcast face with sweat
+ "😩", // 1F629 ; fully-qualified # 😩 weary face
+ "😫", // 1F62B ; fully-qualified # 😫 tired face
+ "🥱", // 1F971 ; fully-qualified # 🥱 yawning face
+ "😤", // 1F624 ; fully-qualified # 😤 face with steam from nose
+ "😡", // 1F621 ; fully-qualified # 😡 pouting face
+ "😠", // 1F620 ; fully-qualified # 😠 angry face
+ "🤬", // 1F92C ; fully-qualified # 🤬 face with symbols on mouth
+ "😈", // 1F608 ; fully-qualified # 😈 smiling face with horns
+ "👿", // 1F47F ; fully-qualified # 👿 angry face with horns
+ "💀", // 1F480 ; fully-qualified # 💀 skull
+ "💩", // 1F4A9 ; fully-qualified # 💩 pile of poo
+ "🤡", // 1F921 ; fully-qualified # 🤡 clown face
+ "👹", // 1F479 ; fully-qualified # 👹 ogre
+ "👺", // 1F47A ; fully-qualified # 👺 goblin
+ "👻", // 1F47B ; fully-qualified # 👻 ghost
+ "👽", // 1F47D ; fully-qualified # 👽 alien
+ "👾", // 1F47E ; fully-qualified # 👾 alien monster
+ "🤖", // 1F916 ; fully-qualified # 🤖 robot
+ "😺", // 1F63A ; fully-qualified # 😺 grinning cat
+ "😸", // 1F638 ; fully-qualified # 😸 grinning cat with smiling eyes
+ "😹", // 1F639 ; fully-qualified # 😹 cat with tears of joy
+ "😻", // 1F63B ; fully-qualified # 😻 smiling cat with heart-eyes
+ "😼", // 1F63C ; fully-qualified # 😼 cat with wry smile
+ "😽", // 1F63D ; fully-qualified # 😽 kissing cat
+ "🙀", // 1F640 ; fully-qualified # 🙀 weary cat
+ "😿", // 1F63F ; fully-qualified # 😿 crying cat
+ "😾", // 1F63E ; fully-qualified # 😾 pouting cat
+ "🙈", // 1F648 ; fully-qualified # 🙈 see-no-evil monkey
+ "🙉", // 1F649 ; fully-qualified # 🙉 hear-no-evil monkey
+ "🙊", // 1F64A ; fully-qualified # 🙊 speak-no-evil monkey
+ "💋", // 1F48B ; fully-qualified # 💋 kiss mark
+ "💌", // 1F48C ; fully-qualified # 💌 love letter
+ "💘", // 1F498 ; fully-qualified # 💘 heart with arrow
+ "💝", // 1F49D ; fully-qualified # 💝 heart with ribbon
+ "💖", // 1F496 ; fully-qualified # 💖 sparkling heart
+ "💗", // 1F497 ; fully-qualified # 💗 growing heart
+ "💓", // 1F493 ; fully-qualified # 💓 beating heart
+ "💞", // 1F49E ; fully-qualified # 💞 revolving hearts
+ "💕", // 1F495 ; fully-qualified # 💕 two hearts
+ "💟", // 1F49F ; fully-qualified # 💟 heart decoration
+ "💔", // 1F494 ; fully-qualified # 💔 broken heart
+ "🧡", // 1F9E1 ; fully-qualified # 🧡 orange heart
+ "💛", // 1F49B ; fully-qualified # 💛 yellow heart
+ "💚", // 1F49A ; fully-qualified # 💚 green heart
+ "💙", // 1F499 ; fully-qualified # 💙 blue heart
+ "💜", // 1F49C ; fully-qualified # 💜 purple heart
+ "🤎", // 1F90E ; fully-qualified # 🤎 brown heart
+ "🖤", // 1F5A4 ; fully-qualified # 🖤 black heart
+ "🤍", // 1F90D ; fully-qualified # 🤍 white heart
+ "💯", // 1F4AF ; fully-qualified # 💯 hundred points
+ "💢", // 1F4A2 ; fully-qualified # 💢 anger symbol
+ "💥", // 1F4A5 ; fully-qualified # 💥 collision
+ "💫", // 1F4AB ; fully-qualified # 💫 dizzy
+ "💦", // 1F4A6 ; fully-qualified # 💦 sweat droplets
+ "💨", // 1F4A8 ; fully-qualified # 💨 dashing away
+ "💣", // 1F4A3 ; fully-qualified # 💣 bomb
+ "💬", // 1F4AC ; fully-qualified # 💬 speech balloon
+ "💭", // 1F4AD ; fully-qualified # 💭 thought balloon
+ "💤", // 1F4A4 ; fully-qualified # 💤 zzz
+ },
+ { // # group: People & Body
+ "👋", // 1F44B ; fully-qualified # 👋 waving hand
+ "🤚", // 1F91A ; fully-qualified # 🤚 raised back of hand
+ "✋", // 270B ; fully-qualified # ✋ raised hand
+ "🖖", // 1F596 ; fully-qualified # 🖖 vulcan salute
+ "👌", // 1F44C ; fully-qualified # 👌 OK hand
+ "🤏", // 1F90F ; fully-qualified # 🤏 pinching hand
+ "🤞", // 1F91E ; fully-qualified # 🤞 crossed fingers
+ "🤟", // 1F91F ; fully-qualified # 🤟 love-you gesture
+ "🤘", // 1F918 ; fully-qualified # 🤘 sign of the horns
+ "🤙", // 1F919 ; fully-qualified # 🤙 call me hand
+ "👈", // 1F448 ; fully-qualified # 👈 backhand index pointing left
+ "👉", // 1F449 ; fully-qualified # 👉 backhand index pointing right
+ "👆", // 1F446 ; fully-qualified # 👆 backhand index pointing up
+ "🖕", // 1F595 ; fully-qualified # 🖕 middle finger
+ "👇", // 1F447 ; fully-qualified # 👇 backhand index pointing down
+ "👍", // 1F44D ; fully-qualified # 👍 thumbs up
+ "👎", // 1F44E ; fully-qualified # 👎 thumbs down
+ "✊", // 270A ; fully-qualified # ✊ raised fist
+ "👊", // 1F44A ; fully-qualified # 👊 oncoming fist
+ "🤛", // 1F91B ; fully-qualified # 🤛 left-facing fist
+ "🤜", // 1F91C ; fully-qualified # 🤜 right-facing fist
+ "👏", // 1F44F ; fully-qualified # 👏 clapping hands
+ "🙌", // 1F64C ; fully-qualified # 🙌 raising hands
+ "👐", // 1F450 ; fully-qualified # 👐 open hands
+ "🤲", // 1F932 ; fully-qualified # 🤲 palms up together
+ "🤝", // 1F91D ; fully-qualified # 🤝 handshake
+ "🙏", // 1F64F ; fully-qualified # 🙏 folded hands
+ "💅", // 1F485 ; fully-qualified # 💅 nail polish
+ "🤳", // 1F933 ; fully-qualified # 🤳 selfie
+ "💪", // 1F4AA ; fully-qualified # 💪 flexed biceps
+ "🦾", // 1F9BE ; fully-qualified # 🦾 mechanical arm
+ "🦿", // 1F9BF ; fully-qualified # 🦿 mechanical leg
+ "🦵", // 1F9B5 ; fully-qualified # 🦵 leg
+ "🦶", // 1F9B6 ; fully-qualified # 🦶 foot
+ "👂", // 1F442 ; fully-qualified # 👂 ear
+ "🦻", // 1F9BB ; fully-qualified # 🦻 ear with hearing aid
+ "👃", // 1F443 ; fully-qualified # 👃 nose
+ "🧠", // 1F9E0 ; fully-qualified # 🧠 brain
+ "🦷", // 1F9B7 ; fully-qualified # 🦷 tooth
+ "🦴", // 1F9B4 ; fully-qualified # 🦴 bone
+ "👀", // 1F440 ; fully-qualified # 👀 eyes
+ "👅", // 1F445 ; fully-qualified # 👅 tongue
+ "👄", // 1F444 ; fully-qualified # 👄 mouth
+ "👶", // 1F476 ; fully-qualified # 👶 baby
+ "🧒", // 1F9D2 ; fully-qualified # 🧒 child
+ "👦", // 1F466 ; fully-qualified # 👦 boy
+ "👧", // 1F467 ; fully-qualified # 👧 girl
+ "🧑", // 1F9D1 ; fully-qualified # 🧑 person
+ "👱", // 1F471 ; fully-qualified # 👱 person: blond hair
+ "👨", // 1F468 ; fully-qualified # 👨 man
+ "🧔", // 1F9D4 ; fully-qualified # 🧔 man: beard
+ "👩", // 1F469 ; fully-qualified # 👩 woman
+ "🧓", // 1F9D3 ; fully-qualified # 🧓 older person
+ "👴", // 1F474 ; fully-qualified # 👴 old man
+ "👵", // 1F475 ; fully-qualified # 👵 old woman
+ "🙍", // 1F64D ; fully-qualified # 🙍 person frowning
+ "🙎", // 1F64E ; fully-qualified # 🙎 person pouting
+ "🙅", // 1F645 ; fully-qualified # 🙅 person gesturing NO
+ "🙆", // 1F646 ; fully-qualified # 🙆 person gesturing OK
+ "💁", // 1F481 ; fully-qualified # 💁 person tipping hand
+ "🙋", // 1F64B ; fully-qualified # 🙋 person raising hand
+ "🧏", // 1F9CF ; fully-qualified # 🧏 deaf person
+ "🙇", // 1F647 ; fully-qualified # 🙇 person bowing
+ "🤦", // 1F926 ; fully-qualified # 🤦 person facepalming
+ "🤷", // 1F937 ; fully-qualified # 🤷 person shrugging
+ "👮", // 1F46E ; fully-qualified # 👮 police officer
+ "💂", // 1F482 ; fully-qualified # 💂 guard
+ "👷", // 1F477 ; fully-qualified # 👷 construction worker
+ "🤴", // 1F934 ; fully-qualified # 🤴 prince
+ "👸", // 1F478 ; fully-qualified # 👸 princess
+ "👳", // 1F473 ; fully-qualified # 👳 person wearing turban
+ "👲", // 1F472 ; fully-qualified # 👲 man with Chinese cap
+ "🧕", // 1F9D5 ; fully-qualified # 🧕 woman with headscarf
+ "🤵", // 1F935 ; fully-qualified # 🤵 man in tuxedo
+ "👰", // 1F470 ; fully-qualified # 👰 bride with veil
+ "🤰", // 1F930 ; fully-qualified # 🤰 pregnant woman
+ "🤱", // 1F931 ; fully-qualified # 🤱 breast-feeding
+ "👼", // 1F47C ; fully-qualified # 👼 baby angel
+ "🎅", // 1F385 ; fully-qualified # 🎅 Santa Claus
+ "🤶", // 1F936 ; fully-qualified # 🤶 Mrs. Claus
+ "🦸", // 1F9B8 ; fully-qualified # 🦸 superhero
+ "🦹", // 1F9B9 ; fully-qualified # 🦹 supervillain
+ "🧙", // 1F9D9 ; fully-qualified # 🧙 mage
+ "🧚", // 1F9DA ; fully-qualified # 🧚 fairy
+ "🧛", // 1F9DB ; fully-qualified # 🧛 vampire
+ "🧜", // 1F9DC ; fully-qualified # 🧜 merperson
+ "🧝", // 1F9DD ; fully-qualified # 🧝 elf
+ "🧞", // 1F9DE ; fully-qualified # 🧞 genie
+ "🧟", // 1F9DF ; fully-qualified # 🧟 zombie
+ "💆", // 1F486 ; fully-qualified # 💆 person getting massage
+ "💇", // 1F487 ; fully-qualified # 💇 person getting haircut
+ "🚶", // 1F6B6 ; fully-qualified # 🚶 person walking
+ "🧍", // 1F9CD ; fully-qualified # 🧍 person standing
+ "🧎", // 1F9CE ; fully-qualified # 🧎 person kneeling
+ "🏃", // 1F3C3 ; fully-qualified # 🏃 person running
+ "💃", // 1F483 ; fully-qualified # 💃 woman dancing
+ "🕺", // 1F57A ; fully-qualified # 🕺 man dancing
+ "👯", // 1F46F ; fully-qualified # 👯 people with bunny ears
+ "🧖", // 1F9D6 ; fully-qualified # 🧖 person in steamy room
+ "🧗", // 1F9D7 ; fully-qualified # 🧗 person climbing
+ "🤺", // 1F93A ; fully-qualified # 🤺 person fencing
+ "🏇", // 1F3C7 ; fully-qualified # 🏇 horse racing
+ "🏂", // 1F3C2 ; fully-qualified # 🏂 snowboarder
+ "🏄", // 1F3C4 ; fully-qualified # 🏄 person surfing
+ "🚣", // 1F6A3 ; fully-qualified # 🚣 person rowing boat
+ "🏊", // 1F3CA ; fully-qualified # 🏊 person swimming
+ "🚴", // 1F6B4 ; fully-qualified # 🚴 person biking
+ "🚵", // 1F6B5 ; fully-qualified # 🚵 person mountain biking
+ "🤸", // 1F938 ; fully-qualified # 🤸 person cartwheeling
+ "🤼", // 1F93C ; fully-qualified # 🤼 people wrestling
+ "🤽", // 1F93D ; fully-qualified # 🤽 person playing water polo
+ "🤾", // 1F93E ; fully-qualified # 🤾 person playing handball
+ "🤹", // 1F939 ; fully-qualified # 🤹 person juggling
+ "🧘", // 1F9D8 ; fully-qualified # 🧘 person in lotus position
+ "🛀", // 1F6C0 ; fully-qualified # 🛀 person taking bath
+ "🛌", // 1F6CC ; fully-qualified # 🛌 person in bed
+ "👭", // 1F46D ; fully-qualified # 👭 women holding hands
+ "👫", // 1F46B ; fully-qualified # 👫 woman and man holding hands
+ "👬", // 1F46C ; fully-qualified # 👬 men holding hands
+ "💏", // 1F48F ; fully-qualified # 💏 kiss
+ "💑", // 1F491 ; fully-qualified # 💑 couple with heart
+ "👪", // 1F46A ; fully-qualified # 👪 family
+ "👤", // 1F464 ; fully-qualified # 👤 bust in silhouette
+ "👥", // 1F465 ; fully-qualified # 👥 busts in silhouette
+ "👣", // 1F463 ; fully-qualified # 👣 footprints
+ },
+ { // # group: Animals & Nature
+ "🐵", // 1F435 ; fully-qualified # 🐵 monkey face
+ "🐒", // 1F412 ; fully-qualified # 🐒 monkey
+ "🦍", // 1F98D ; fully-qualified # 🦍 gorilla
+ "🦧", // 1F9A7 ; fully-qualified # 🦧 orangutan
+ "🐶", // 1F436 ; fully-qualified # 🐶 dog face
+ "🐕", // 1F415 ; fully-qualified # 🐕 dog
+ "🦮", // 1F9AE ; fully-qualified # 🦮 guide dog
+ "🐩", // 1F429 ; fully-qualified # 🐩 poodle
+ "🐺", // 1F43A ; fully-qualified # 🐺 wolf
+ "🦊", // 1F98A ; fully-qualified # 🦊 fox
+ "🦝", // 1F99D ; fully-qualified # 🦝 raccoon
+ "🐱", // 1F431 ; fully-qualified # 🐱 cat face
+ "🐈", // 1F408 ; fully-qualified # 🐈 cat
+ "🦁", // 1F981 ; fully-qualified # 🦁 lion
+ "🐯", // 1F42F ; fully-qualified # 🐯 tiger face
+ "🐅", // 1F405 ; fully-qualified # 🐅 tiger
+ "🐆", // 1F406 ; fully-qualified # 🐆 leopard
+ "🐴", // 1F434 ; fully-qualified # 🐴 horse face
+ "🐎", // 1F40E ; fully-qualified # 🐎 horse
+ "🦄", // 1F984 ; fully-qualified # 🦄 unicorn
+ "🦓", // 1F993 ; fully-qualified # 🦓 zebra
+ "🦌", // 1F98C ; fully-qualified # 🦌 deer
+ "🐮", // 1F42E ; fully-qualified # 🐮 cow face
+ "🐂", // 1F402 ; fully-qualified # 🐂 ox
+ "🐃", // 1F403 ; fully-qualified # 🐃 water buffalo
+ "🐄", // 1F404 ; fully-qualified # 🐄 cow
+ "🐷", // 1F437 ; fully-qualified # 🐷 pig face
+ "🐖", // 1F416 ; fully-qualified # 🐖 pig
+ "🐗", // 1F417 ; fully-qualified # 🐗 boar
+ "🐽", // 1F43D ; fully-qualified # 🐽 pig nose
+ "🐏", // 1F40F ; fully-qualified # 🐏 ram
+ "🐑", // 1F411 ; fully-qualified # 🐑 ewe
+ "🐐", // 1F410 ; fully-qualified # 🐐 goat
+ "🐪", // 1F42A ; fully-qualified # 🐪 camel
+ "🐫", // 1F42B ; fully-qualified # 🐫 two-hump camel
+ "🦙", // 1F999 ; fully-qualified # 🦙 llama
+ "🦒", // 1F992 ; fully-qualified # 🦒 giraffe
+ "🐘", // 1F418 ; fully-qualified # 🐘 elephant
+ "🦏", // 1F98F ; fully-qualified # 🦏 rhinoceros
+ "🦛", // 1F99B ; fully-qualified # 🦛 hippopotamus
+ "🐭", // 1F42D ; fully-qualified # 🐭 mouse face
+ "🐁", // 1F401 ; fully-qualified # 🐁 mouse
+ "🐀", // 1F400 ; fully-qualified # 🐀 rat
+ "🐹", // 1F439 ; fully-qualified # 🐹 hamster
+ "🐰", // 1F430 ; fully-qualified # 🐰 rabbit face
+ "🐇", // 1F407 ; fully-qualified # 🐇 rabbit
+ "🦔", // 1F994 ; fully-qualified # 🦔 hedgehog
+ "🦇", // 1F987 ; fully-qualified # 🦇 bat
+ "🐻", // 1F43B ; fully-qualified # 🐻 bear
+ "🐨", // 1F428 ; fully-qualified # 🐨 koala
+ "🐼", // 1F43C ; fully-qualified # 🐼 panda
+ "🦥", // 1F9A5 ; fully-qualified # 🦥 sloth
+ "🦦", // 1F9A6 ; fully-qualified # 🦦 otter
+ "🦨", // 1F9A8 ; fully-qualified # 🦨 skunk
+ "🦘", // 1F998 ; fully-qualified # 🦘 kangaroo
+ "🦡", // 1F9A1 ; fully-qualified # 🦡 badger
+ "🐾", // 1F43E ; fully-qualified # 🐾 paw prints
+ "🦃", // 1F983 ; fully-qualified # 🦃 turkey
+ "🐔", // 1F414 ; fully-qualified # 🐔 chicken
+ "🐓", // 1F413 ; fully-qualified # 🐓 rooster
+ "🐣", // 1F423 ; fully-qualified # 🐣 hatching chick
+ "🐤", // 1F424 ; fully-qualified # 🐤 baby chick
+ "🐥", // 1F425 ; fully-qualified # 🐥 front-facing baby chick
+ "🐦", // 1F426 ; fully-qualified # 🐦 bird
+ "🐧", // 1F427 ; fully-qualified # 🐧 penguin
+ "🦅", // 1F985 ; fully-qualified # 🦅 eagle
+ "🦆", // 1F986 ; fully-qualified # 🦆 duck
+ "🦢", // 1F9A2 ; fully-qualified # 🦢 swan
+ "🦉", // 1F989 ; fully-qualified # 🦉 owl
+ "🦩", // 1F9A9 ; fully-qualified # 🦩 flamingo
+ "🦚", // 1F99A ; fully-qualified # 🦚 peacock
+ "🦜", // 1F99C ; fully-qualified # 🦜 parrot
+ "🐸", // 1F438 ; fully-qualified # 🐸 frog
+ "🐊", // 1F40A ; fully-qualified # 🐊 crocodile
+ "🐢", // 1F422 ; fully-qualified # 🐢 turtle
+ "🦎", // 1F98E ; fully-qualified # 🦎 lizard
+ "🐍", // 1F40D ; fully-qualified # 🐍 snake
+ "🐲", // 1F432 ; fully-qualified # 🐲 dragon face
+ "🐉", // 1F409 ; fully-qualified # 🐉 dragon
+ "🦕", // 1F995 ; fully-qualified # 🦕 sauropod
+ "🦖", // 1F996 ; fully-qualified # 🦖 T-Rex
+ "🐳", // 1F433 ; fully-qualified # 🐳 spouting whale
+ "🐋", // 1F40B ; fully-qualified # 🐋 whale
+ "🐬", // 1F42C ; fully-qualified # 🐬 dolphin
+ "🐟", // 1F41F ; fully-qualified # 🐟 fish
+ "🐠", // 1F420 ; fully-qualified # 🐠 tropical fish
+ "🐡", // 1F421 ; fully-qualified # 🐡 blowfish
+ "🦈", // 1F988 ; fully-qualified # 🦈 shark
+ "🐙", // 1F419 ; fully-qualified # 🐙 octopus
+ "🐚", // 1F41A ; fully-qualified # 🐚 spiral shell
+ "🐌", // 1F40C ; fully-qualified # 🐌 snail
+ "🦋", // 1F98B ; fully-qualified # 🦋 butterfly
+ "🐛", // 1F41B ; fully-qualified # 🐛 bug
+ "🐜", // 1F41C ; fully-qualified # 🐜 ant
+ "🐝", // 1F41D ; fully-qualified # 🐝 honeybee
+ "🐞", // 1F41E ; fully-qualified # 🐞 lady beetle
+ "🦗", // 1F997 ; fully-qualified # 🦗 cricket
+ "🦂", // 1F982 ; fully-qualified # 🦂 scorpion
+ "🦟", // 1F99F ; fully-qualified # 🦟 mosquito
+ "🦠", // 1F9A0 ; fully-qualified # 🦠 microbe
+ "💐", // 1F490 ; fully-qualified # 💐 bouquet
+ "🌸", // 1F338 ; fully-qualified # 🌸 cherry blossom
+ "💮", // 1F4AE ; fully-qualified # 💮 white flower
+ "🌹", // 1F339 ; fully-qualified # 🌹 rose
+ "🥀", // 1F940 ; fully-qualified # 🥀 wilted flower
+ "🌺", // 1F33A ; fully-qualified # 🌺 hibiscus
+ "🌻", // 1F33B ; fully-qualified # 🌻 sunflower
+ "🌼", // 1F33C ; fully-qualified # 🌼 blossom
+ "🌷", // 1F337 ; fully-qualified # 🌷 tulip
+ "🌱", // 1F331 ; fully-qualified # 🌱 seedling
+ "🌲", // 1F332 ; fully-qualified # 🌲 evergreen tree
+ "🌳", // 1F333 ; fully-qualified # 🌳 deciduous tree
+ "🌴", // 1F334 ; fully-qualified # 🌴 palm tree
+ "🌵", // 1F335 ; fully-qualified # 🌵 cactus
+ "🌾", // 1F33E ; fully-qualified # 🌾 sheaf of rice
+ "🌿", // 1F33F ; fully-qualified # 🌿 herb
+ "🍀", // 1F340 ; fully-qualified # 🍀 four leaf clover
+ "🍁", // 1F341 ; fully-qualified # 🍁 maple leaf
+ "🍂", // 1F342 ; fully-qualified # 🍂 fallen leaf
+ "🍃", // 1F343 ; fully-qualified # 🍃 leaf fluttering in wind
+ },
+ { // # group: Food & Drink
+ "🍇", // 1F347 ; fully-qualified # 🍇 grapes
+ "🍈", // 1F348 ; fully-qualified # 🍈 melon
+ "🍉", // 1F349 ; fully-qualified # 🍉 watermelon
+ "🍊", // 1F34A ; fully-qualified # 🍊 tangerine
+ "🍋", // 1F34B ; fully-qualified # 🍋 lemon
+ "🍌", // 1F34C ; fully-qualified # 🍌 banana
+ "🍍", // 1F34D ; fully-qualified # 🍍 pineapple
+ "🥭", // 1F96D ; fully-qualified # 🥭 mango
+ "🍎", // 1F34E ; fully-qualified # 🍎 red apple
+ "🍏", // 1F34F ; fully-qualified # 🍏 green apple
+ "🍐", // 1F350 ; fully-qualified # 🍐 pear
+ "🍑", // 1F351 ; fully-qualified # 🍑 peach
+ "🍒", // 1F352 ; fully-qualified # 🍒 cherries
+ "🍓", // 1F353 ; fully-qualified # 🍓 strawberry
+ "🥝", // 1F95D ; fully-qualified # 🥝 kiwi fruit
+ "🍅", // 1F345 ; fully-qualified # 🍅 tomato
+ "🥥", // 1F965 ; fully-qualified # 🥥 coconut
+ "🥑", // 1F951 ; fully-qualified # 🥑 avocado
+ "🍆", // 1F346 ; fully-qualified # 🍆 eggplant
+ "🥔", // 1F954 ; fully-qualified # 🥔 potato
+ "🥕", // 1F955 ; fully-qualified # 🥕 carrot
+ "🌽", // 1F33D ; fully-qualified # 🌽 ear of corn
+ "🥒", // 1F952 ; fully-qualified # 🥒 cucumber
+ "🥬", // 1F96C ; fully-qualified # 🥬 leafy green
+ "🥦", // 1F966 ; fully-qualified # 🥦 broccoli
+ "🧄", // 1F9C4 ; fully-qualified # 🧄 garlic
+ "🧅", // 1F9C5 ; fully-qualified # 🧅 onion
+ "🍄", // 1F344 ; fully-qualified # 🍄 mushroom
+ "🥜", // 1F95C ; fully-qualified # 🥜 peanuts
+ "🌰", // 1F330 ; fully-qualified # 🌰 chestnut
+ "🍞", // 1F35E ; fully-qualified # 🍞 bread
+ "🥐", // 1F950 ; fully-qualified # 🥐 croissant
+ "🥖", // 1F956 ; fully-qualified # 🥖 baguette bread
+ "🥨", // 1F968 ; fully-qualified # 🥨 pretzel
+ "🥯", // 1F96F ; fully-qualified # 🥯 bagel
+ "🥞", // 1F95E ; fully-qualified # 🥞 pancakes
+ "🧇", // 1F9C7 ; fully-qualified # 🧇 waffle
+ "🧀", // 1F9C0 ; fully-qualified # 🧀 cheese wedge
+ "🍖", // 1F356 ; fully-qualified # 🍖 meat on bone
+ "🍗", // 1F357 ; fully-qualified # 🍗 poultry leg
+ "🥩", // 1F969 ; fully-qualified # 🥩 cut of meat
+ "🥓", // 1F953 ; fully-qualified # 🥓 bacon
+ "🍔", // 1F354 ; fully-qualified # 🍔 hamburger
+ "🍟", // 1F35F ; fully-qualified # 🍟 french fries
+ "🍕", // 1F355 ; fully-qualified # 🍕 pizza
+ "🌭", // 1F32D ; fully-qualified # 🌭 hot dog
+ "🥪", // 1F96A ; fully-qualified # 🥪 sandwich
+ "🌮", // 1F32E ; fully-qualified # 🌮 taco
+ "🌯", // 1F32F ; fully-qualified # 🌯 burrito
+ "🥙", // 1F959 ; fully-qualified # 🥙 stuffed flatbread
+ "🧆", // 1F9C6 ; fully-qualified # 🧆 falafel
+ "🥚", // 1F95A ; fully-qualified # 🥚 egg
+ "🍳", // 1F373 ; fully-qualified # 🍳 cooking
+ "🥘", // 1F958 ; fully-qualified # 🥘 shallow pan of food
+ "🍲", // 1F372 ; fully-qualified # 🍲 pot of food
+ "🥣", // 1F963 ; fully-qualified # 🥣 bowl with spoon
+ "🥗", // 1F957 ; fully-qualified # 🥗 green salad
+ "🍿", // 1F37F ; fully-qualified # 🍿 popcorn
+ "🧈", // 1F9C8 ; fully-qualified # 🧈 butter
+ "🧂", // 1F9C2 ; fully-qualified # 🧂 salt
+ "🥫", // 1F96B ; fully-qualified # 🥫 canned food
+ "🍱", // 1F371 ; fully-qualified # 🍱 bento box
+ "🍘", // 1F358 ; fully-qualified # 🍘 rice cracker
+ "🍙", // 1F359 ; fully-qualified # 🍙 rice ball
+ "🍚", // 1F35A ; fully-qualified # 🍚 cooked rice
+ "🍛", // 1F35B ; fully-qualified # 🍛 curry rice
+ "🍜", // 1F35C ; fully-qualified # 🍜 steaming bowl
+ "🍝", // 1F35D ; fully-qualified # 🍝 spaghetti
+ "🍠", // 1F360 ; fully-qualified # 🍠 roasted sweet potato
+ "🍢", // 1F362 ; fully-qualified # 🍢 oden
+ "🍣", // 1F363 ; fully-qualified # 🍣 sushi
+ "🍤", // 1F364 ; fully-qualified # 🍤 fried shrimp
+ "🍥", // 1F365 ; fully-qualified # 🍥 fish cake with swirl
+ "🥮", // 1F96E ; fully-qualified # 🥮 moon cake
+ "🍡", // 1F361 ; fully-qualified # 🍡 dango
+ "🥟", // 1F95F ; fully-qualified # 🥟 dumpling
+ "🥠", // 1F960 ; fully-qualified # 🥠 fortune cookie
+ "🥡", // 1F961 ; fully-qualified # 🥡 takeout box
+ "🦀", // 1F980 ; fully-qualified # 🦀 crab
+ "🦞", // 1F99E ; fully-qualified # 🦞 lobster
+ "🦐", // 1F990 ; fully-qualified # 🦐 shrimp
+ "🦑", // 1F991 ; fully-qualified # 🦑 squid
+ "🦪", // 1F9AA ; fully-qualified # 🦪 oyster
+ "🍦", // 1F366 ; fully-qualified # 🍦 soft ice cream
+ "🍧", // 1F367 ; fully-qualified # 🍧 shaved ice
+ "🍨", // 1F368 ; fully-qualified # 🍨 ice cream
+ "🍩", // 1F369 ; fully-qualified # 🍩 doughnut
+ "🍪", // 1F36A ; fully-qualified # 🍪 cookie
+ "🎂", // 1F382 ; fully-qualified # 🎂 birthday cake
+ "🍰", // 1F370 ; fully-qualified # 🍰 shortcake
+ "🧁", // 1F9C1 ; fully-qualified # 🧁 cupcake
+ "🥧", // 1F967 ; fully-qualified # 🥧 pie
+ "🍫", // 1F36B ; fully-qualified # 🍫 chocolate bar
+ "🍬", // 1F36C ; fully-qualified # 🍬 candy
+ "🍭", // 1F36D ; fully-qualified # 🍭 lollipop
+ "🍮", // 1F36E ; fully-qualified # 🍮 custard
+ "🍯", // 1F36F ; fully-qualified # 🍯 honey pot
+ "🍼", // 1F37C ; fully-qualified # 🍼 baby bottle
+ "🥛", // 1F95B ; fully-qualified # 🥛 glass of milk
+ "☕", // 2615 ; fully-qualified # ☕ hot beverage
+ "🍵", // 1F375 ; fully-qualified # 🍵 teacup without handle
+ "🍶", // 1F376 ; fully-qualified # 🍶 sake
+ "🍾", // 1F37E ; fully-qualified # 🍾 bottle with popping cork
+ "🍷", // 1F377 ; fully-qualified # 🍷 wine glass
+ "🍸", // 1F378 ; fully-qualified # 🍸 cocktail glass
+ "🍹", // 1F379 ; fully-qualified # 🍹 tropical drink
+ "🍺", // 1F37A ; fully-qualified # 🍺 beer mug
+ "🍻", // 1F37B ; fully-qualified # 🍻 clinking beer mugs
+ "🥂", // 1F942 ; fully-qualified # 🥂 clinking glasses
+ "🥃", // 1F943 ; fully-qualified # 🥃 tumbler glass
+ "🥤", // 1F964 ; fully-qualified # 🥤 cup with straw
+ "🧃", // 1F9C3 ; fully-qualified # 🧃 beverage box
+ "🧉", // 1F9C9 ; fully-qualified # 🧉 mate
+ "🧊", // 1F9CA ; fully-qualified # 🧊 ice cube
+ "🥢", // 1F962 ; fully-qualified # 🥢 chopsticks
+ "🍴", // 1F374 ; fully-qualified # 🍴 fork and knife
+ "🥄", // 1F944 ; fully-qualified # 🥄 spoon
+ "🔪", // 1F52A ; fully-qualified # 🔪 kitchen knife
+ "🏺", // 1F3FA ; fully-qualified # 🏺 amphora
+ },
+ { // # group: Travel & Places
+ "🌍", // 1F30D ; fully-qualified # 🌍 globe showing Europe-Africa
+ "🌎", // 1F30E ; fully-qualified # 🌎 globe showing Americas
+ "🌏", // 1F30F ; fully-qualified # 🌏 globe showing Asia-Australia
+ "🌐", // 1F310 ; fully-qualified # 🌐 globe with meridians
+ "🗾", // 1F5FE ; fully-qualified # 🗾 map of Japan
+ "🧭", // 1F9ED ; fully-qualified # 🧭 compass
+ "🌋", // 1F30B ; fully-qualified # 🌋 volcano
+ "🗻", // 1F5FB ; fully-qualified # 🗻 mount fuji
+ "🧱", // 1F9F1 ; fully-qualified # 🧱 brick
+ "🏠", // 1F3E0 ; fully-qualified # 🏠 house
+ "🏡", // 1F3E1 ; fully-qualified # 🏡 house with garden
+ "🏢", // 1F3E2 ; fully-qualified # 🏢 office building
+ "🏣", // 1F3E3 ; fully-qualified # 🏣 Japanese post office
+ "🏤", // 1F3E4 ; fully-qualified # 🏤 post office
+ "🏥", // 1F3E5 ; fully-qualified # 🏥 hospital
+ "🏦", // 1F3E6 ; fully-qualified # 🏦 bank
+ "🏨", // 1F3E8 ; fully-qualified # 🏨 hotel
+ "🏩", // 1F3E9 ; fully-qualified # 🏩 love hotel
+ "🏪", // 1F3EA ; fully-qualified # 🏪 convenience store
+ "🏫", // 1F3EB ; fully-qualified # 🏫 school
+ "🏬", // 1F3EC ; fully-qualified # 🏬 department store
+ "🏭", // 1F3ED ; fully-qualified # 🏭 factory
+ "🏯", // 1F3EF ; fully-qualified # 🏯 Japanese castle
+ "🏰", // 1F3F0 ; fully-qualified # 🏰 castle
+ "💒", // 1F492 ; fully-qualified # 💒 wedding
+ "🗼", // 1F5FC ; fully-qualified # 🗼 Tokyo tower
+ "🗽", // 1F5FD ; fully-qualified # 🗽 Statue of Liberty
+ "⛪", // 26EA ; fully-qualified # ⛪ church
+ "🕌", // 1F54C ; fully-qualified # 🕌 mosque
+ "🛕", // 1F6D5 ; fully-qualified # 🛕 hindu temple
+ "🕍", // 1F54D ; fully-qualified # 🕍 synagogue
+ "🕋", // 1F54B ; fully-qualified # 🕋 kaaba
+ "⛲", // 26F2 ; fully-qualified # ⛲ fountain
+ "⛺", // 26FA ; fully-qualified # ⛺ tent
+ "🌁", // 1F301 ; fully-qualified # 🌁 foggy
+ "🌃", // 1F303 ; fully-qualified # 🌃 night with stars
+ "🌄", // 1F304 ; fully-qualified # 🌄 sunrise over mountains
+ "🌅", // 1F305 ; fully-qualified # 🌅 sunrise
+ "🌆", // 1F306 ; fully-qualified # 🌆 cityscape at dusk
+ "🌇", // 1F307 ; fully-qualified # 🌇 sunset
+ "🌉", // 1F309 ; fully-qualified # 🌉 bridge at night
+ "🎠", // 1F3A0 ; fully-qualified # 🎠 carousel horse
+ "🎡", // 1F3A1 ; fully-qualified # 🎡 ferris wheel
+ "🎢", // 1F3A2 ; fully-qualified # 🎢 roller coaster
+ "💈", // 1F488 ; fully-qualified # 💈 barber pole
+ "🎪", // 1F3AA ; fully-qualified # 🎪 circus tent
+ "🚂", // 1F682 ; fully-qualified # 🚂 locomotive
+ "🚃", // 1F683 ; fully-qualified # 🚃 railway car
+ "🚄", // 1F684 ; fully-qualified # 🚄 high-speed train
+ "🚅", // 1F685 ; fully-qualified # 🚅 bullet train
+ "🚆", // 1F686 ; fully-qualified # 🚆 train
+ "🚇", // 1F687 ; fully-qualified # 🚇 metro
+ "🚈", // 1F688 ; fully-qualified # 🚈 light rail
+ "🚉", // 1F689 ; fully-qualified # 🚉 station
+ "🚊", // 1F68A ; fully-qualified # 🚊 tram
+ "🚝", // 1F69D ; fully-qualified # 🚝 monorail
+ "🚞", // 1F69E ; fully-qualified # 🚞 mountain railway
+ "🚋", // 1F68B ; fully-qualified # 🚋 tram car
+ "🚌", // 1F68C ; fully-qualified # 🚌 bus
+ "🚍", // 1F68D ; fully-qualified # 🚍 oncoming bus
+ "🚎", // 1F68E ; fully-qualified # 🚎 trolleybus
+ "🚐", // 1F690 ; fully-qualified # 🚐 minibus
+ "🚑", // 1F691 ; fully-qualified # 🚑 ambulance
+ "🚒", // 1F692 ; fully-qualified # 🚒 fire engine
+ "🚓", // 1F693 ; fully-qualified # 🚓 police car
+ "🚔", // 1F694 ; fully-qualified # 🚔 oncoming police car
+ "🚕", // 1F695 ; fully-qualified # 🚕 taxi
+ "🚖", // 1F696 ; fully-qualified # 🚖 oncoming taxi
+ "🚗", // 1F697 ; fully-qualified # 🚗 automobile
+ "🚘", // 1F698 ; fully-qualified # 🚘 oncoming automobile
+ "🚙", // 1F699 ; fully-qualified # 🚙 sport utility vehicle
+ "🚚", // 1F69A ; fully-qualified # 🚚 delivery truck
+ "🚛", // 1F69B ; fully-qualified # 🚛 articulated lorry
+ "🚜", // 1F69C ; fully-qualified # 🚜 tractor
+ "🛵", // 1F6F5 ; fully-qualified # 🛵 motor scooter
+ "🦽", // 1F9BD ; fully-qualified # 🦽 manual wheelchair
+ "🦼", // 1F9BC ; fully-qualified # 🦼 motorized wheelchair
+ "🛺", // 1F6FA ; fully-qualified # 🛺 auto rickshaw
+ "🚲", // 1F6B2 ; fully-qualified # 🚲 bicycle
+ "🛴", // 1F6F4 ; fully-qualified # 🛴 kick scooter
+ "🛹", // 1F6F9 ; fully-qualified # 🛹 skateboard
+ "🚏", // 1F68F ; fully-qualified # 🚏 bus stop
+ "⛽", // 26FD ; fully-qualified # ⛽ fuel pump
+ "🚨", // 1F6A8 ; fully-qualified # 🚨 police car light
+ "🚥", // 1F6A5 ; fully-qualified # 🚥 horizontal traffic light
+ "🚦", // 1F6A6 ; fully-qualified # 🚦 vertical traffic light
+ "🛑", // 1F6D1 ; fully-qualified # 🛑 stop sign
+ "🚧", // 1F6A7 ; fully-qualified # 🚧 construction
+ "⚓", // 2693 ; fully-qualified # ⚓ anchor
+ "⛵", // 26F5 ; fully-qualified # ⛵ sailboat
+ "🛶", // 1F6F6 ; fully-qualified # 🛶 canoe
+ "🚤", // 1F6A4 ; fully-qualified # 🚤 speedboat
+ "🚢", // 1F6A2 ; fully-qualified # 🚢 ship
+ "🛫", // 1F6EB ; fully-qualified # 🛫 airplane departure
+ "🛬", // 1F6EC ; fully-qualified # 🛬 airplane arrival
+ "🪂", // 1FA82 ; fully-qualified # 🪂 parachute
+ "💺", // 1F4BA ; fully-qualified # 💺 seat
+ "🚁", // 1F681 ; fully-qualified # 🚁 helicopter
+ "🚟", // 1F69F ; fully-qualified # 🚟 suspension railway
+ "🚠", // 1F6A0 ; fully-qualified # 🚠 mountain cableway
+ "🚡", // 1F6A1 ; fully-qualified # 🚡 aerial tramway
+ "🚀", // 1F680 ; fully-qualified # 🚀 rocket
+ "🛸", // 1F6F8 ; fully-qualified # 🛸 flying saucer
+ "🧳", // 1F9F3 ; fully-qualified # 🧳 luggage
+ "⌛", // 231B ; fully-qualified # ⌛ hourglass done
+ "⏳", // 23F3 ; fully-qualified # ⏳ hourglass not done
+ "⌚", // 231A ; fully-qualified # ⌚ watch
+ "⏰", // 23F0 ; fully-qualified # ⏰ alarm clock
+ "🕛", // 1F55B ; fully-qualified # 🕛 twelve o’clock
+ "🕧", // 1F567 ; fully-qualified # 🕧 twelve-thirty
+ "🕐", // 1F550 ; fully-qualified # 🕐 one o’clock
+ "🕜", // 1F55C ; fully-qualified # 🕜 one-thirty
+ "🕑", // 1F551 ; fully-qualified # 🕑 two o’clock
+ "🕝", // 1F55D ; fully-qualified # 🕝 two-thirty
+ "🕒", // 1F552 ; fully-qualified # 🕒 three o’clock
+ "🕞", // 1F55E ; fully-qualified # 🕞 three-thirty
+ "🕓", // 1F553 ; fully-qualified # 🕓 four o’clock
+ "🕟", // 1F55F ; fully-qualified # 🕟 four-thirty
+ "🕔", // 1F554 ; fully-qualified # 🕔 five o’clock
+ "🕠", // 1F560 ; fully-qualified # 🕠 five-thirty
+ "🕕", // 1F555 ; fully-qualified # 🕕 six o’clock
+ "🕡", // 1F561 ; fully-qualified # 🕡 six-thirty
+ "🕖", // 1F556 ; fully-qualified # 🕖 seven o’clock
+ "🕢", // 1F562 ; fully-qualified # 🕢 seven-thirty
+ "🕗", // 1F557 ; fully-qualified # 🕗 eight o’clock
+ "🕣", // 1F563 ; fully-qualified # 🕣 eight-thirty
+ "🕘", // 1F558 ; fully-qualified # 🕘 nine o’clock
+ "🕤", // 1F564 ; fully-qualified # 🕤 nine-thirty
+ "🕙", // 1F559 ; fully-qualified # 🕙 ten o’clock
+ "🕥", // 1F565 ; fully-qualified # 🕥 ten-thirty
+ "🕚", // 1F55A ; fully-qualified # 🕚 eleven o’clock
+ "🕦", // 1F566 ; fully-qualified # 🕦 eleven-thirty
+ "🌑", // 1F311 ; fully-qualified # 🌑 new moon
+ "🌒", // 1F312 ; fully-qualified # 🌒 waxing crescent moon
+ "🌓", // 1F313 ; fully-qualified # 🌓 first quarter moon
+ "🌔", // 1F314 ; fully-qualified # 🌔 waxing gibbous moon
+ "🌕", // 1F315 ; fully-qualified # 🌕 full moon
+ "🌖", // 1F316 ; fully-qualified # 🌖 waning gibbous moon
+ "🌗", // 1F317 ; fully-qualified # 🌗 last quarter moon
+ "🌘", // 1F318 ; fully-qualified # 🌘 waning crescent moon
+ "🌙", // 1F319 ; fully-qualified # 🌙 crescent moon
+ "🌚", // 1F31A ; fully-qualified # 🌚 new moon face
+ "🌛", // 1F31B ; fully-qualified # 🌛 first quarter moon face
+ "🌜", // 1F31C ; fully-qualified # 🌜 last quarter moon face
+ "🌝", // 1F31D ; fully-qualified # 🌝 full moon face
+ "🌞", // 1F31E ; fully-qualified # 🌞 sun with face
+ "🪐", // 1FA90 ; fully-qualified # 🪐 ringed planet
+ "⭐", // 2B50 ; fully-qualified # ⭐ star
+ "🌟", // 1F31F ; fully-qualified # 🌟 glowing star
+ "🌠", // 1F320 ; fully-qualified # 🌠 shooting star
+ "🌌", // 1F30C ; fully-qualified # 🌌 milky way
+ "⛅", // 26C5 ; fully-qualified # ⛅ sun behind cloud
+ "🌀", // 1F300 ; fully-qualified # 🌀 cyclone
+ "🌈", // 1F308 ; fully-qualified # 🌈 rainbow
+ "🌂", // 1F302 ; fully-qualified # 🌂 closed umbrella
+ "☔", // 2614 ; fully-qualified # ☔ umbrella with rain drops
+ "⚡", // 26A1 ; fully-qualified # ⚡ high voltage
+ "⛄", // 26C4 ; fully-qualified # ⛄ snowman without snow
+ "🔥", // 1F525 ; fully-qualified # 🔥 fire
+ "💧", // 1F4A7 ; fully-qualified # 💧 droplet
+ "🌊", // 1F30A ; fully-qualified # 🌊 water wave
+ },
+ { // # group: Activities
+ "🎃", // 1F383 ; fully-qualified # 🎃 jack-o-lantern
+ "🎄", // 1F384 ; fully-qualified # 🎄 Christmas tree
+ "🎆", // 1F386 ; fully-qualified # 🎆 fireworks
+ "🎇", // 1F387 ; fully-qualified # 🎇 sparkler
+ "🧨", // 1F9E8 ; fully-qualified # 🧨 firecracker
+ "✨", // 2728 ; fully-qualified # ✨ sparkles
+ "🎈", // 1F388 ; fully-qualified # 🎈 balloon
+ "🎉", // 1F389 ; fully-qualified # 🎉 party popper
+ "🎊", // 1F38A ; fully-qualified # 🎊 confetti ball
+ "🎋", // 1F38B ; fully-qualified # 🎋 tanabata tree
+ "🎍", // 1F38D ; fully-qualified # 🎍 pine decoration
+ "🎎", // 1F38E ; fully-qualified # 🎎 Japanese dolls
+ "🎏", // 1F38F ; fully-qualified # 🎏 carp streamer
+ "🎐", // 1F390 ; fully-qualified # 🎐 wind chime
+ "🎑", // 1F391 ; fully-qualified # 🎑 moon viewing ceremony
+ "🧧", // 1F9E7 ; fully-qualified # 🧧 red envelope
+ "🎀", // 1F380 ; fully-qualified # 🎀 ribbon
+ "🎁", // 1F381 ; fully-qualified # 🎁 wrapped gift
+ "🎫", // 1F3AB ; fully-qualified # 🎫 ticket
+ "🏆", // 1F3C6 ; fully-qualified # 🏆 trophy
+ "🏅", // 1F3C5 ; fully-qualified # 🏅 sports medal
+ "🥇", // 1F947 ; fully-qualified # 🥇 1st place medal
+ "🥈", // 1F948 ; fully-qualified # 🥈 2nd place medal
+ "🥉", // 1F949 ; fully-qualified # 🥉 3rd place medal
+ "⚽", // 26BD ; fully-qualified # ⚽ soccer ball
+ "⚾", // 26BE ; fully-qualified # ⚾ baseball
+ "🥎", // 1F94E ; fully-qualified # 🥎 softball
+ "🏀", // 1F3C0 ; fully-qualified # 🏀 basketball
+ "🏐", // 1F3D0 ; fully-qualified # 🏐 volleyball
+ "🏈", // 1F3C8 ; fully-qualified # 🏈 american football
+ "🏉", // 1F3C9 ; fully-qualified # 🏉 rugby football
+ "🎾", // 1F3BE ; fully-qualified # 🎾 tennis
+ "🥏", // 1F94F ; fully-qualified # 🥏 flying disc
+ "🎳", // 1F3B3 ; fully-qualified # 🎳 bowling
+ "🏏", // 1F3CF ; fully-qualified # 🏏 cricket game
+ "🏑", // 1F3D1 ; fully-qualified # 🏑 field hockey
+ "🏒", // 1F3D2 ; fully-qualified # 🏒 ice hockey
+ "🥍", // 1F94D ; fully-qualified # 🥍 lacrosse
+ "🏓", // 1F3D3 ; fully-qualified # 🏓 ping pong
+ "🏸", // 1F3F8 ; fully-qualified # 🏸 badminton
+ "🥊", // 1F94A ; fully-qualified # 🥊 boxing glove
+ "🥋", // 1F94B ; fully-qualified # 🥋 martial arts uniform
+ "🥅", // 1F945 ; fully-qualified # 🥅 goal net
+ "⛳", // 26F3 ; fully-qualified # ⛳ flag in hole
+ "🎣", // 1F3A3 ; fully-qualified # 🎣 fishing pole
+ "🤿", // 1F93F ; fully-qualified # 🤿 diving mask
+ "🎽", // 1F3BD ; fully-qualified # 🎽 running shirt
+ "🎿", // 1F3BF ; fully-qualified # 🎿 skis
+ "🛷", // 1F6F7 ; fully-qualified # 🛷 sled
+ "🥌", // 1F94C ; fully-qualified # 🥌 curling stone
+ "🎯", // 1F3AF ; fully-qualified # 🎯 direct hit
+ "🪀", // 1FA80 ; fully-qualified # 🪀 yo-yo
+ "🪁", // 1FA81 ; fully-qualified # 🪁 kite
+ "🎱", // 1F3B1 ; fully-qualified # 🎱 pool 8 ball
+ "🔮", // 1F52E ; fully-qualified # 🔮 crystal ball
+ "🧿", // 1F9FF ; fully-qualified # 🧿 nazar amulet
+ "🎮", // 1F3AE ; fully-qualified # 🎮 video game
+ "🎰", // 1F3B0 ; fully-qualified # 🎰 slot machine
+ "🎲", // 1F3B2 ; fully-qualified # 🎲 game die
+ "🧩", // 1F9E9 ; fully-qualified # 🧩 puzzle piece
+ "🧸", // 1F9F8 ; fully-qualified # 🧸 teddy bear
+ "🃏", // 1F0CF ; fully-qualified # 🃏 joker
+ "🀄", // 1F004 ; fully-qualified # 🀄 mahjong red dragon
+ "🎴", // 1F3B4 ; fully-qualified # 🎴 flower playing cards
+ "🎭", // 1F3AD ; fully-qualified # 🎭 performing arts
+ "🎨", // 1F3A8 ; fully-qualified # 🎨 artist palette
+ "🧵", // 1F9F5 ; fully-qualified # 🧵 thread
+ "🧶", // 1F9F6 ; fully-qualified # 🧶 yarn
+ },
+ { // # group: Objects
+ "👓", // 1F453 ; fully-qualified # 👓 glasses
+ "🥽", // 1F97D ; fully-qualified # 🥽 goggles
+ "🥼", // 1F97C ; fully-qualified # 🥼 lab coat
+ "🦺", // 1F9BA ; fully-qualified # 🦺 safety vest
+ "👔", // 1F454 ; fully-qualified # 👔 necktie
+ "👕", // 1F455 ; fully-qualified # 👕 t-shirt
+ "👖", // 1F456 ; fully-qualified # 👖 jeans
+ "🧣", // 1F9E3 ; fully-qualified # 🧣 scarf
+ "🧤", // 1F9E4 ; fully-qualified # 🧤 gloves
+ "🧥", // 1F9E5 ; fully-qualified # 🧥 coat
+ "🧦", // 1F9E6 ; fully-qualified # 🧦 socks
+ "👗", // 1F457 ; fully-qualified # 👗 dress
+ "👘", // 1F458 ; fully-qualified # 👘 kimono
+ "🥻", // 1F97B ; fully-qualified # 🥻 sari
+ "🩱", // 1FA71 ; fully-qualified # 🩱 one-piece swimsuit
+ "🩲", // 1FA72 ; fully-qualified # 🩲 swim brief
+ "🩳", // 1FA73 ; fully-qualified # 🩳 shorts
+ "👙", // 1F459 ; fully-qualified # 👙 bikini
+ "👚", // 1F45A ; fully-qualified # 👚 woman’s clothes
+ "👛", // 1F45B ; fully-qualified # 👛 purse
+ "👜", // 1F45C ; fully-qualified # 👜 handbag
+ "👝", // 1F45D ; fully-qualified # 👝 clutch bag
+ "🎒", // 1F392 ; fully-qualified # 🎒 backpack
+ "👞", // 1F45E ; fully-qualified # 👞 man’s shoe
+ "👟", // 1F45F ; fully-qualified # 👟 running shoe
+ "🥾", // 1F97E ; fully-qualified # 🥾 hiking boot
+ "🥿", // 1F97F ; fully-qualified # 🥿 flat shoe
+ "👠", // 1F460 ; fully-qualified # 👠 high-heeled shoe
+ "👡", // 1F461 ; fully-qualified # 👡 woman’s sandal
+ "🩰", // 1FA70 ; fully-qualified # 🩰 ballet shoes
+ "👢", // 1F462 ; fully-qualified # 👢 woman’s boot
+ "👑", // 1F451 ; fully-qualified # 👑 crown
+ "👒", // 1F452 ; fully-qualified # 👒 woman’s hat
+ "🎩", // 1F3A9 ; fully-qualified # 🎩 top hat
+ "🎓", // 1F393 ; fully-qualified # 🎓 graduation cap
+ "🧢", // 1F9E2 ; fully-qualified # 🧢 billed cap
+ "📿", // 1F4FF ; fully-qualified # 📿 prayer beads
+ "💄", // 1F484 ; fully-qualified # 💄 lipstick
+ "💍", // 1F48D ; fully-qualified # 💍 ring
+ "💎", // 1F48E ; fully-qualified # 💎 gem stone
+ "🔇", // 1F507 ; fully-qualified # 🔇 muted speaker
+ "🔈", // 1F508 ; fully-qualified # 🔈 speaker low volume
+ "🔉", // 1F509 ; fully-qualified # 🔉 speaker medium volume
+ "🔊", // 1F50A ; fully-qualified # 🔊 speaker high volume
+ "📢", // 1F4E2 ; fully-qualified # 📢 loudspeaker
+ "📣", // 1F4E3 ; fully-qualified # 📣 megaphone
+ "📯", // 1F4EF ; fully-qualified # 📯 postal horn
+ "🔔", // 1F514 ; fully-qualified # 🔔 bell
+ "🔕", // 1F515 ; fully-qualified # 🔕 bell with slash
+ "🎼", // 1F3BC ; fully-qualified # 🎼 musical score
+ "🎵", // 1F3B5 ; fully-qualified # 🎵 musical note
+ "🎶", // 1F3B6 ; fully-qualified # 🎶 musical notes
+ "🎤", // 1F3A4 ; fully-qualified # 🎤 microphone
+ "🎧", // 1F3A7 ; fully-qualified # 🎧 headphone
+ "📻", // 1F4FB ; fully-qualified # 📻 radio
+ "🎷", // 1F3B7 ; fully-qualified # 🎷 saxophone
+ "🎸", // 1F3B8 ; fully-qualified # 🎸 guitar
+ "🎹", // 1F3B9 ; fully-qualified # 🎹 musical keyboard
+ "🎺", // 1F3BA ; fully-qualified # 🎺 trumpet
+ "🎻", // 1F3BB ; fully-qualified # 🎻 violin
+ "🪕", // 1FA95 ; fully-qualified # 🪕 banjo
+ "🥁", // 1F941 ; fully-qualified # 🥁 drum
+ "📱", // 1F4F1 ; fully-qualified # 📱 mobile phone
+ "📲", // 1F4F2 ; fully-qualified # 📲 mobile phone with arrow
+ "📞", // 1F4DE ; fully-qualified # 📞 telephone receiver
+ "📟", // 1F4DF ; fully-qualified # 📟 pager
+ "📠", // 1F4E0 ; fully-qualified # 📠 fax machine
+ "🔋", // 1F50B ; fully-qualified # 🔋 battery
+ "🔌", // 1F50C ; fully-qualified # 🔌 electric plug
+ "💻", // 1F4BB ; fully-qualified # 💻 laptop computer
+ "💽", // 1F4BD ; fully-qualified # 💽 computer disk
+ "💾", // 1F4BE ; fully-qualified # 💾 floppy disk
+ "💿", // 1F4BF ; fully-qualified # 💿 optical disk
+ "📀", // 1F4C0 ; fully-qualified # 📀 dvd
+ "🧮", // 1F9EE ; fully-qualified # 🧮 abacus
+ "🎥", // 1F3A5 ; fully-qualified # 🎥 movie camera
+ "🎬", // 1F3AC ; fully-qualified # 🎬 clapper board
+ "📺", // 1F4FA ; fully-qualified # 📺 television
+ "📷", // 1F4F7 ; fully-qualified # 📷 camera
+ "📸", // 1F4F8 ; fully-qualified # 📸 camera with flash
+ "📹", // 1F4F9 ; fully-qualified # 📹 video camera
+ "📼", // 1F4FC ; fully-qualified # 📼 videocassette
+ "🔍", // 1F50D ; fully-qualified # 🔍 magnifying glass tilted left
+ "🔎", // 1F50E ; fully-qualified # 🔎 magnifying glass tilted right
+ "💡", // 1F4A1 ; fully-qualified # 💡 light bulb
+ "🔦", // 1F526 ; fully-qualified # 🔦 flashlight
+ "🏮", // 1F3EE ; fully-qualified # 🏮 red paper lantern
+ "🪔", // 1FA94 ; fully-qualified # 🪔 diya lamp
+ "📔", // 1F4D4 ; fully-qualified # 📔 notebook with decorative cover
+ "📕", // 1F4D5 ; fully-qualified # 📕 closed book
+ "📖", // 1F4D6 ; fully-qualified # 📖 open book
+ "📗", // 1F4D7 ; fully-qualified # 📗 green book
+ "📘", // 1F4D8 ; fully-qualified # 📘 blue book
+ "📙", // 1F4D9 ; fully-qualified # 📙 orange book
+ "📚", // 1F4DA ; fully-qualified # 📚 books
+ "📓", // 1F4D3 ; fully-qualified # 📓 notebook
+ "📒", // 1F4D2 ; fully-qualified # 📒 ledger
+ "📃", // 1F4C3 ; fully-qualified # 📃 page with curl
+ "📜", // 1F4DC ; fully-qualified # 📜 scroll
+ "📄", // 1F4C4 ; fully-qualified # 📄 page facing up
+ "📰", // 1F4F0 ; fully-qualified # 📰 newspaper
+ "📑", // 1F4D1 ; fully-qualified # 📑 bookmark tabs
+ "🔖", // 1F516 ; fully-qualified # 🔖 bookmark
+ "💰", // 1F4B0 ; fully-qualified # 💰 money bag
+ "💴", // 1F4B4 ; fully-qualified # 💴 yen banknote
+ "💵", // 1F4B5 ; fully-qualified # 💵 dollar banknote
+ "💶", // 1F4B6 ; fully-qualified # 💶 euro banknote
+ "💷", // 1F4B7 ; fully-qualified # 💷 pound banknote
+ "💸", // 1F4B8 ; fully-qualified # 💸 money with wings
+ "💳", // 1F4B3 ; fully-qualified # 💳 credit card
+ "🧾", // 1F9FE ; fully-qualified # 🧾 receipt
+ "💹", // 1F4B9 ; fully-qualified # 💹 chart increasing with yen
+ "💱", // 1F4B1 ; fully-qualified # 💱 currency exchange
+ "💲", // 1F4B2 ; fully-qualified # 💲 heavy dollar sign
+ "📧", // 1F4E7 ; fully-qualified # 📧 e-mail
+ "📨", // 1F4E8 ; fully-qualified # 📨 incoming envelope
+ "📩", // 1F4E9 ; fully-qualified # 📩 envelope with arrow
+ "📤", // 1F4E4 ; fully-qualified # 📤 outbox tray
+ "📥", // 1F4E5 ; fully-qualified # 📥 inbox tray
+ "📦", // 1F4E6 ; fully-qualified # 📦 package
+ "📫", // 1F4EB ; fully-qualified # 📫 closed mailbox with raised flag
+ "📪", // 1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag
+ "📬", // 1F4EC ; fully-qualified # 📬 open mailbox with raised flag
+ "📭", // 1F4ED ; fully-qualified # 📭 open mailbox with lowered flag
+ "📮", // 1F4EE ; fully-qualified # 📮 postbox
+ "📝", // 1F4DD ; fully-qualified # 📝 memo
+ "💼", // 1F4BC ; fully-qualified # 💼 briefcase
+ "📁", // 1F4C1 ; fully-qualified # 📁 file folder
+ "📂", // 1F4C2 ; fully-qualified # 📂 open file folder
+ "📅", // 1F4C5 ; fully-qualified # 📅 calendar
+ "📆", // 1F4C6 ; fully-qualified # 📆 tear-off calendar
+ "📇", // 1F4C7 ; fully-qualified # 📇 card index
+ "📈", // 1F4C8 ; fully-qualified # 📈 chart increasing
+ "📉", // 1F4C9 ; fully-qualified # 📉 chart decreasing
+ "📊", // 1F4CA ; fully-qualified # 📊 bar chart
+ "📋", // 1F4CB ; fully-qualified # 📋 clipboard
+ "📌", // 1F4CC ; fully-qualified # 📌 pushpin
+ "📍", // 1F4CD ; fully-qualified # 📍 round pushpin
+ "📎", // 1F4CE ; fully-qualified # 📎 paperclip
+ "📏", // 1F4CF ; fully-qualified # 📏 straight ruler
+ "📐", // 1F4D0 ; fully-qualified # 📐 triangular ruler
+ "🔒", // 1F512 ; fully-qualified # 🔒 locked
+ "🔓", // 1F513 ; fully-qualified # 🔓 unlocked
+ "🔏", // 1F50F ; fully-qualified # 🔏 locked with pen
+ "🔐", // 1F510 ; fully-qualified # 🔐 locked with key
+ "🔑", // 1F511 ; fully-qualified # 🔑 key
+ "🔨", // 1F528 ; fully-qualified # 🔨 hammer
+ "🪓", // 1FA93 ; fully-qualified # 🪓 axe
+ "🔫", // 1F52B ; fully-qualified # 🔫 pistol
+ "🏹", // 1F3F9 ; fully-qualified # 🏹 bow and arrow
+ "🔧", // 1F527 ; fully-qualified # 🔧 wrench
+ "🔩", // 1F529 ; fully-qualified # 🔩 nut and bolt
+ "🦯", // 1F9AF ; fully-qualified # 🦯 probing cane
+ "🔗", // 1F517 ; fully-qualified # 🔗 link
+ "🧰", // 1F9F0 ; fully-qualified # 🧰 toolbox
+ "🧲", // 1F9F2 ; fully-qualified # 🧲 magnet
+ "🧪", // 1F9EA ; fully-qualified # 🧪 test tube
+ "🧫", // 1F9EB ; fully-qualified # 🧫 petri dish
+ "🧬", // 1F9EC ; fully-qualified # 🧬 dna
+ "🔬", // 1F52C ; fully-qualified # 🔬 microscope
+ "🔭", // 1F52D ; fully-qualified # 🔭 telescope
+ "📡", // 1F4E1 ; fully-qualified # 📡 satellite antenna
+ "💉", // 1F489 ; fully-qualified # 💉 syringe
+ "🩸", // 1FA78 ; fully-qualified # 🩸 drop of blood
+ "💊", // 1F48A ; fully-qualified # 💊 pill
+ "🩹", // 1FA79 ; fully-qualified # 🩹 adhesive bandage
+ "🩺", // 1FA7A ; fully-qualified # 🩺 stethoscope
+ "🚪", // 1F6AA ; fully-qualified # 🚪 door
+ "🪑", // 1FA91 ; fully-qualified # 🪑 chair
+ "🚽", // 1F6BD ; fully-qualified # 🚽 toilet
+ "🚿", // 1F6BF ; fully-qualified # 🚿 shower
+ "🛁", // 1F6C1 ; fully-qualified # 🛁 bathtub
+ "🪒", // 1FA92 ; fully-qualified # 🪒 razor
+ "🧴", // 1F9F4 ; fully-qualified # 🧴 lotion bottle
+ "🧷", // 1F9F7 ; fully-qualified # 🧷 safety pin
+ "🧹", // 1F9F9 ; fully-qualified # 🧹 broom
+ "🧺", // 1F9FA ; fully-qualified # 🧺 basket
+ "🧻", // 1F9FB ; fully-qualified # 🧻 roll of paper
+ "🧼", // 1F9FC ; fully-qualified # 🧼 soap
+ "🧽", // 1F9FD ; fully-qualified # 🧽 sponge
+ "🧯", // 1F9EF ; fully-qualified # 🧯 fire extinguisher
+ "🛒", // 1F6D2 ; fully-qualified # 🛒 shopping cart
+ "🚬", // 1F6AC ; fully-qualified # 🚬 cigarette
+ "🗿", // 1F5FF ; fully-qualified # 🗿 moai
+ },
+ { // # group: Symbols
+ "🏧", // 1F3E7 ; fully-qualified # 🏧 ATM sign
+ "🚮", // 1F6AE ; fully-qualified # 🚮 litter in bin sign
+ "🚰", // 1F6B0 ; fully-qualified # 🚰 potable water
+ "♿", // 267F ; fully-qualified # ♿ wheelchair symbol
+ "🚹", // 1F6B9 ; fully-qualified # 🚹 men’s room
+ "🚺", // 1F6BA ; fully-qualified # 🚺 women’s room
+ "🚻", // 1F6BB ; fully-qualified # 🚻 restroom
+ "🚼", // 1F6BC ; fully-qualified # 🚼 baby symbol
+ "🚾", // 1F6BE ; fully-qualified # 🚾 water closet
+ "🛂", // 1F6C2 ; fully-qualified # 🛂 passport control
+ "🛃", // 1F6C3 ; fully-qualified # 🛃 customs
+ "🛄", // 1F6C4 ; fully-qualified # 🛄 baggage claim
+ "🛅", // 1F6C5 ; fully-qualified # 🛅 left luggage
+ "🚸", // 1F6B8 ; fully-qualified # 🚸 children crossing
+ "⛔", // 26D4 ; fully-qualified # ⛔ no entry
+ "🚫", // 1F6AB ; fully-qualified # 🚫 prohibited
+ "🚳", // 1F6B3 ; fully-qualified # 🚳 no bicycles
+ "🚭", // 1F6AD ; fully-qualified # 🚭 no smoking
+ "🚯", // 1F6AF ; fully-qualified # 🚯 no littering
+ "🚱", // 1F6B1 ; fully-qualified # 🚱 non-potable water
+ "🚷", // 1F6B7 ; fully-qualified # 🚷 no pedestrians
+ "📵", // 1F4F5 ; fully-qualified # 📵 no mobile phones
+ "🔞", // 1F51E ; fully-qualified # 🔞 no one under eighteen
+ "🔃", // 1F503 ; fully-qualified # 🔃 clockwise vertical arrows
+ "🔄", // 1F504 ; fully-qualified # 🔄 counterclockwise arrows button
+ "🔙", // 1F519 ; fully-qualified # 🔙 BACK arrow
+ "🔚", // 1F51A ; fully-qualified # 🔚 END arrow
+ "🔛", // 1F51B ; fully-qualified # 🔛 ON! arrow
+ "🔜", // 1F51C ; fully-qualified # 🔜 SOON arrow
+ "🔝", // 1F51D ; fully-qualified # 🔝 TOP arrow
+ "🛐", // 1F6D0 ; fully-qualified # 🛐 place of worship
+ "🕎", // 1F54E ; fully-qualified # 🕎 menorah
+ "🔯", // 1F52F ; fully-qualified # 🔯 dotted six-pointed star
+ "♈", // 2648 ; fully-qualified # ♈ Aries
+ "♉", // 2649 ; fully-qualified # ♉ Taurus
+ "♊", // 264A ; fully-qualified # ♊ Gemini
+ "♋", // 264B ; fully-qualified # ♋ Cancer
+ "♌", // 264C ; fully-qualified # ♌ Leo
+ "♍", // 264D ; fully-qualified # ♍ Virgo
+ "♎", // 264E ; fully-qualified # ♎ Libra
+ "♏", // 264F ; fully-qualified # ♏ Scorpio
+ "♐", // 2650 ; fully-qualified # ♐ Sagittarius
+ "♑", // 2651 ; fully-qualified # ♑ Capricorn
+ "♒", // 2652 ; fully-qualified # ♒ Aquarius
+ "♓", // 2653 ; fully-qualified # ♓ Pisces
+ "⛎", // 26CE ; fully-qualified # ⛎ Ophiuchus
+ "🔀", // 1F500 ; fully-qualified # 🔀 shuffle tracks button
+ "🔁", // 1F501 ; fully-qualified # 🔁 repeat button
+ "🔂", // 1F502 ; fully-qualified # 🔂 repeat single button
+ "⏩", // 23E9 ; fully-qualified # ⏩ fast-forward button
+ "⏪", // 23EA ; fully-qualified # ⏪ fast reverse button
+ "🔼", // 1F53C ; fully-qualified # 🔼 upwards button
+ "⏫", // 23EB ; fully-qualified # ⏫ fast up button
+ "🔽", // 1F53D ; fully-qualified # 🔽 downwards button
+ "⏬", // 23EC ; fully-qualified # ⏬ fast down button
+ "🎦", // 1F3A6 ; fully-qualified # 🎦 cinema
+ "🔅", // 1F505 ; fully-qualified # 🔅 dim button
+ "🔆", // 1F506 ; fully-qualified # 🔆 bright button
+ "📶", // 1F4F6 ; fully-qualified # 📶 antenna bars
+ "📳", // 1F4F3 ; fully-qualified # 📳 vibration mode
+ "📴", // 1F4F4 ; fully-qualified # 📴 mobile phone off
+ "🔱", // 1F531 ; fully-qualified # 🔱 trident emblem
+ "📛", // 1F4DB ; fully-qualified # 📛 name badge
+ "🔰", // 1F530 ; fully-qualified # 🔰 Japanese symbol for beginner
+ "⭕", // 2B55 ; fully-qualified # ⭕ hollow red circle
+ "✅", // 2705 ; fully-qualified # ✅ check mark button
+ "❌", // 274C ; fully-qualified # ❌ cross mark
+ "❎", // 274E ; fully-qualified # ❎ cross mark button
+ "➕", // 2795 ; fully-qualified # ➕ plus sign
+ "➖", // 2796 ; fully-qualified # ➖ minus sign
+ "➗", // 2797 ; fully-qualified # ➗ division sign
+ "➰", // 27B0 ; fully-qualified # ➰ curly loop
+ "➿", // 27BF ; fully-qualified # ➿ double curly loop
+ "❓", // 2753 ; fully-qualified # ❓ question mark
+ "❔", // 2754 ; fully-qualified # ❔ white question mark
+ "❕", // 2755 ; fully-qualified # ❕ white exclamation mark
+ "❗", // 2757 ; fully-qualified # ❗ exclamation mark
+ "🔟", // 1F51F ; fully-qualified # 🔟 keycap: 10
+ "🔠", // 1F520 ; fully-qualified # 🔠 input latin uppercase
+ "🔡", // 1F521 ; fully-qualified # 🔡 input latin lowercase
+ "🔢", // 1F522 ; fully-qualified # 🔢 input numbers
+ "🔣", // 1F523 ; fully-qualified # 🔣 input symbols
+ "🔤", // 1F524 ; fully-qualified # 🔤 input latin letters
+ "🆎", // 1F18E ; fully-qualified # 🆎 AB button (blood type)
+ "🆑", // 1F191 ; fully-qualified # 🆑 CL button
+ "🆒", // 1F192 ; fully-qualified # 🆒 COOL button
+ "🆓", // 1F193 ; fully-qualified # 🆓 FREE button
+ "🆔", // 1F194 ; fully-qualified # 🆔 ID button
+ "🆕", // 1F195 ; fully-qualified # 🆕 NEW button
+ "🆖", // 1F196 ; fully-qualified # 🆖 NG button
+ "🆗", // 1F197 ; fully-qualified # 🆗 OK button
+ "🆘", // 1F198 ; fully-qualified # 🆘 SOS button
+ "🆙", // 1F199 ; fully-qualified # 🆙 UP! button
+ "🆚", // 1F19A ; fully-qualified # 🆚 VS button
+ "🈁", // 1F201 ; fully-qualified # 🈁 Japanese “here” button
+ "🈶", // 1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button
+ "🈯", // 1F22F ; fully-qualified # 🈯 Japanese “reserved” button
+ "🉐", // 1F250 ; fully-qualified # 🉐 Japanese “bargain” button
+ "🈹", // 1F239 ; fully-qualified # 🈹 Japanese “discount” button
+ "🈚", // 1F21A ; fully-qualified # 🈚 Japanese “free of charge” button
+ "🈲", // 1F232 ; fully-qualified # 🈲 Japanese “prohibited” button
+ "🉑", // 1F251 ; fully-qualified # 🉑 Japanese “acceptable” button
+ "🈸", // 1F238 ; fully-qualified # 🈸 Japanese “application” button
+ "🈴", // 1F234 ; fully-qualified # 🈴 Japanese “passing grade” button
+ "🈳", // 1F233 ; fully-qualified # 🈳 Japanese “vacancy” button
+ "🈺", // 1F23A ; fully-qualified # 🈺 Japanese “open for business” button
+ "🈵", // 1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button
+ "🔴", // 1F534 ; fully-qualified # 🔴 red circle
+ "🟠", // 1F7E0 ; fully-qualified # 🟠 orange circle
+ "🟡", // 1F7E1 ; fully-qualified # 🟡 yellow circle
+ "🟢", // 1F7E2 ; fully-qualified # 🟢 green circle
+ "🔵", // 1F535 ; fully-qualified # 🔵 blue circle
+ "🟣", // 1F7E3 ; fully-qualified # 🟣 purple circle
+ "🟤", // 1F7E4 ; fully-qualified # 🟤 brown circle
+ "⚫", // 26AB ; fully-qualified # ⚫ black circle
+ "⚪", // 26AA ; fully-qualified # ⚪ white circle
+ "🟥", // 1F7E5 ; fully-qualified # 🟥 red square
+ "🟧", // 1F7E7 ; fully-qualified # 🟧 orange square
+ "🟨", // 1F7E8 ; fully-qualified # 🟨 yellow square
+ "🟩", // 1F7E9 ; fully-qualified # 🟩 green square
+ "🟦", // 1F7E6 ; fully-qualified # 🟦 blue square
+ "🟪", // 1F7EA ; fully-qualified # 🟪 purple square
+ "🟫", // 1F7EB ; fully-qualified # 🟫 brown square
+ "⬛", // 2B1B ; fully-qualified # ⬛ black large square
+ "⬜", // 2B1C ; fully-qualified # ⬜ white large square
+ "◾", // 25FE ; fully-qualified # ◾ black medium-small square
+ "◽", // 25FD ; fully-qualified # ◽ white medium-small square
+ "🔶", // 1F536 ; fully-qualified # 🔶 large orange diamond
+ "🔷", // 1F537 ; fully-qualified # 🔷 large blue diamond
+ "🔸", // 1F538 ; fully-qualified # 🔸 small orange diamond
+ "🔹", // 1F539 ; fully-qualified # 🔹 small blue diamond
+ "🔺", // 1F53A ; fully-qualified # 🔺 red triangle pointed up
+ "🔻", // 1F53B ; fully-qualified # 🔻 red triangle pointed down
+ "💠", // 1F4A0 ; fully-qualified # 💠 diamond with a dot
+ "🔘", // 1F518 ; fully-qualified # 🔘 radio button
+ "🔳", // 1F533 ; fully-qualified # 🔳 white square button
+ "🔲", // 1F532 ; fully-qualified # 🔲 black square button
+ },
+ { // # group: Flags
+ "🏁", // 1F3C1 ; fully-qualified # 🏁 chequered flag
+ "🚩", // 1F6A9 ; fully-qualified # 🚩 triangular flag
+ "🎌", // 1F38C ; fully-qualified # 🎌 crossed flags
+ "🏴", // 1F3F4 ; fully-qualified # 🏴 black flag
+ }
+ };
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/HTMLEdit.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/HTMLEdit.java
index bb34425..b01e99c 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/HTMLEdit.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/HTMLEdit.java
@@ -1,17 +1,15 @@
package com.keylesspalace.tusky.util;
-import androidx.annotation.IntDef;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import android.text.Editable;
import android.text.Selection;
-import android.text.Spannable;
import android.widget.EditText;
+import androidx.annotation.NonNull;
import me.thanel.markdownedit.SelectionUtils;
public class HTMLEdit {
- private HTMLEdit() { /* cannot be instantiated */ }
+ private HTMLEdit() {
+ /* cannot be instantiated */
+ }
public static void addBold(@NonNull Editable text) {
surroundSelectionWith(text, "<b>", "</b>");
@@ -61,7 +59,7 @@ public class HTMLEdit {
public static void addLink(@NonNull EditText editText) {
addLink(editText.getText());
}
-
+
/**
* Inserts a markdown code block to the specified EditText at the currently selected position.
*
@@ -80,7 +78,8 @@ public class HTMLEdit {
addCode(editText.getText());
}
- public static void surroundSelectionWith(@NonNull Editable text, @NonNull String surroundText, @NonNull String surroundText2) {
+ public static void surroundSelectionWith(
+ @NonNull Editable text, @NonNull String surroundText, @NonNull String surroundText2) {
if (!SelectionUtils.hasSelection(text)) {
SelectionUtils.selectWordAroundCursor(text);
}
@@ -91,7 +90,7 @@ public class HTMLEdit {
StringBuilder result = new StringBuilder();
result.append(surroundText).append(selectedText).append(surroundText2);
-
+
int charactersToGoBack = 0;
if (selectedText.length() == 0) {
charactersToGoBack = surroundText2.length();
@@ -100,5 +99,4 @@ public class HTMLEdit {
SelectionUtils.replaceSelectedText(text, result);
Selection.setSelection(text, selectionStart + result.length() - charactersToGoBack);
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.java
index 27f3dff..9f83cc6 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.java
@@ -11,7 +11,6 @@ package com.keylesspalace.tusky.util;
import android.net.Uri;
import androidx.annotation.Nullable;
-
import java.util.ArrayList;
import java.util.List;
@@ -148,8 +147,8 @@ public class HttpHeaderLink {
* @return the link matching the given relation type
*/
@Nullable
- public static HttpHeaderLink findByRelationType(List<HttpHeaderLink> links,
- String relationType) {
+ public static HttpHeaderLink findByRelationType(
+ List<HttpHeaderLink> links, String relationType) {
for (HttpHeaderLink link : links) {
for (Parameter parameter : link.parameters) {
if (parameter.name.equals("rel") && parameter.value.equals(relationType)) {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
index 7c3b68a..4bdc5e6 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
@@ -18,7 +18,6 @@ package com.keylesspalace.tusky.util;
import android.content.ContentResolver;
import android.net.Uri;
import androidx.annotation.Nullable;
-
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
index c05f224..02ce107 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
@@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -29,26 +28,22 @@ import android.text.style.URLSpan;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.preference.PreferenceManager;
-
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.LinkListener;
-
import java.net.URI;
import java.net.URISyntaxException;
public class LinkHelper {
public static String getDomain(String urlString) {
// sometimes URL can be null due to Pleroma bug
- if(urlString == null)
- return "";
-
+ if (urlString == null) return "";
+
URI uri;
try {
uri = new URI(urlString);
@@ -56,7 +51,7 @@ public class LinkHelper {
return "";
}
String host = uri.getHost();
- if(host == null) {
+ if (host == null) {
return "";
} else if (host.startsWith("www.")) {
return host.substring(4);
@@ -74,8 +69,11 @@ public class LinkHelper {
* @param mentions any '@' mentions which are known to be in the content
* @param listener to notify about particular spans that are clicked
*/
- public static void setClickableText(TextView view, CharSequence content,
- @Nullable Status.Mention[] mentions, final LinkListener listener) {
+ public static void setClickableText(
+ TextView view,
+ CharSequence content,
+ @Nullable Status.Mention[] mentions,
+ final LinkListener listener) {
SpannableStringBuilder builder = SpannableStringBuilder.valueOf(content);
URLSpan[] urlSpans = builder.getSpans(0, content.length(), URLSpan.class);
for (URLSpan span : urlSpans) {
@@ -87,10 +85,13 @@ public class LinkHelper {
if (text.charAt(0) == '#') {
final String tag = text.subSequence(1, text.length()).toString();
- customSpan = new ClickableSpanNoUnderline() {
- @Override
- public void onClick(@NonNull View widget) { listener.onViewTag(tag); }
- };
+ customSpan =
+ new ClickableSpanNoUnderline() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ listener.onViewTag(tag);
+ }
+ };
} else if (text.charAt(0) == '@' && mentions != null && mentions.length > 0) {
String accountUsername = text.subSequence(1, text.length()).toString();
/* There may be multiple matches for users on different instances with the same
@@ -108,20 +109,24 @@ public class LinkHelper {
}
if (id != null) {
final String accountId = id;
- customSpan = new ClickableSpanNoUnderline() {
- @Override
- public void onClick(@NonNull View widget) { listener.onViewAccount(accountId); }
- };
+ customSpan =
+ new ClickableSpanNoUnderline() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ listener.onViewAccount(accountId);
+ }
+ };
}
}
if (customSpan == null) {
- customSpan = new CustomURLSpan(span.getURL()) {
- @Override
- public void onClick(View widget) {
- listener.onViewUrl(getURL());
- }
- };
+ customSpan =
+ new CustomURLSpan(span.getURL()) {
+ @Override
+ public void onClick(View widget) {
+ listener.onViewUrl(getURL());
+ }
+ };
}
builder.removeSpan(span);
builder.setSpan(customSpan, start, end, flags);
@@ -129,8 +134,8 @@ public class LinkHelper {
/* Add zero-width space after links in end of line to fix its too large hitbox.
* See also : https://github.com/tuskyapp/Tusky/issues/846
* https://github.com/tuskyapp/Tusky/pull/916 */
- if (end >= builder.length() ||
- builder.subSequence(end, end + 1).toString().equals("\n")){
+ if (end >= builder.length()
+ || builder.subSequence(end, end + 1).toString().equals("\n")) {
builder.insert(end, "\u200B");
}
}
@@ -161,10 +166,13 @@ public class LinkHelper {
for (Status.Mention mention : mentions) {
String accountUsername = mention.getLocalUsername();
final String accountId = mention.getId();
- ClickableSpan customSpan = new ClickableSpanNoUnderline() {
- @Override
- public void onClick(@NonNull View widget) { listener.onViewAccount(accountId); }
- };
+ ClickableSpan customSpan =
+ new ClickableSpanNoUnderline() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ listener.onViewAccount(accountId);
+ }
+ };
end += 1 + accountUsername.length(); // length of @ + username
flags = builder.getSpanFlags(customSpan);
@@ -201,13 +209,13 @@ public class LinkHelper {
* @param context context
*/
public static void openLink(String url, Context context) {
- if(url == null)
- return;
-
+ if (url == null) return;
+
Uri uri = Uri.parse(url).normalizeScheme();
- boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean("customTabs", false);
+ boolean useCustomTabs =
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean("customTabs", false);
if (useCustomTabs) {
openLinkInCustomTab(uri, context);
} else {
@@ -231,8 +239,7 @@ public class LinkHelper {
}
/**
- * tries to open a link in a custom tab
- * falls back to browser if not possible
+ * tries to open a link in a custom tab falls back to browser if not possible
*
* @param uri the uri to open
* @param context context
@@ -242,16 +249,18 @@ public class LinkHelper {
int navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor);
int navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor);
- CustomTabColorSchemeParams colorSchemeParams = new CustomTabColorSchemeParams.Builder()
- .setToolbarColor(toolbarColor)
- .setNavigationBarColor(navigationbarColor)
- .setNavigationBarDividerColor(navigationbarDividerColor)
- .build();
+ CustomTabColorSchemeParams colorSchemeParams =
+ new CustomTabColorSchemeParams.Builder()
+ .setToolbarColor(toolbarColor)
+ .setNavigationBarColor(navigationbarColor)
+ .setNavigationBarDividerColor(navigationbarDividerColor)
+ .build();
- CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
- .setDefaultColorSchemeParams(colorSchemeParams)
- .setShowTitle(true)
- .build();
+ CustomTabsIntent customTabsIntent =
+ new CustomTabsIntent.Builder()
+ .setDefaultColorSchemeParams(colorSchemeParams)
+ .setShowTitle(true)
+ .build();
try {
customTabsIntent.launchUrl(context, uri);
@@ -259,7 +268,5 @@ public class LinkHelper {
Log.w("LinkHelper", "Activity was not found for intent " + customTabsIntent);
openLinkInBrowser(uri, context);
}
-
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/PagingRequestHelper.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/PagingRequestHelper.java
index 4f7d3ef..389bce4 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/PagingRequestHelper.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/PagingRequestHelper.java
@@ -24,17 +24,19 @@ import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* A helper class for {@link androidx.paging.PagedList.BoundaryCallback BoundaryCallback}s and
* {@link androidx.paging.DataSource}s to help with tracking network requests.
- * <p>
- * It is designed to support 3 types of requests, {@link RequestType#INITIAL INITIAL},
- * {@link RequestType#BEFORE BEFORE} and {@link RequestType#AFTER AFTER} and runs only 1 request
- * for each of them via {@link #runIfNotRunning(RequestType, Request)}.
- * <p>
- * It tracks a {@link Status} and an {@code error} for each {@link RequestType}.
- * <p>
- * A sample usage of this class to limit requests looks like this:
+ *
+ * <p>It is designed to support 3 types of requests, {@link RequestType#INITIAL INITIAL}, {@link
+ * RequestType#BEFORE BEFORE} and {@link RequestType#AFTER AFTER} and runs only 1 request for each
+ * of them via {@link #runIfNotRunning(RequestType, Request)}.
+ *
+ * <p>It tracks a {@link Status} and an {@code error} for each {@link RequestType}.
+ *
+ * <p>A sample usage of this class to limit requests looks like this:
+ *
* <pre>
* class PagingBoundaryCallback extends PagedList.BoundaryCallback&lt;MyItem> {
* // TODO replace with an executor from your application
@@ -82,9 +84,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
* }
* }
* </pre>
- * <p>
- * The helper provides an API to observe combined request status, which can be reported back to the
- * application based on your business rules.
+ *
+ * <p>The helper provides an API to observe combined request status, which can be reported back to
+ * the application based on your business rules.
+ *
* <pre>
* MutableLiveData&lt;PagingRequestHelper.Status> combined = new MutableLiveData&lt;>();
* helper.addListener(status -> {
@@ -106,16 +109,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class PagingRequestHelper {
private final Object mLock = new Object();
private final Executor mRetryService;
+
@GuardedBy("mLock")
- private final RequestQueue[] mRequestQueues = new RequestQueue[]
- {new RequestQueue(RequestType.INITIAL),
- new RequestQueue(RequestType.BEFORE),
- new RequestQueue(RequestType.AFTER)};
- @NonNull
- final CopyOnWriteArrayList<Listener> mListeners = new CopyOnWriteArrayList<>();
+ private final RequestQueue[] mRequestQueues =
+ new RequestQueue[] {
+ new RequestQueue(RequestType.INITIAL),
+ new RequestQueue(RequestType.BEFORE),
+ new RequestQueue(RequestType.AFTER)
+ };
+
+ @NonNull final CopyOnWriteArrayList<Listener> mListeners = new CopyOnWriteArrayList<>();
/**
- * Creates a new PagingRequestHelper with the given {@link Executor} which is used to run
- * retry actions.
+ * Creates a new PagingRequestHelper with the given {@link Executor} which is used to run retry
+ * actions.
*
* @param retryService The {@link Executor} that can run the retry actions.
*/
@@ -144,10 +150,10 @@ public class PagingRequestHelper {
/**
* Runs the given {@link Request} if no other requests in the given request type is already
* running.
- * <p>
- * If run, the request will be run in the current thread.
*
- * @param type The type of the request.
+ * <p>If run, the request will be run in the current thread.
+ *
+ * @param type The type of the request.
* @param request The request to run.
* @return True if the request is run, false otherwise.
*/
@@ -176,24 +182,27 @@ public class PagingRequestHelper {
wrapper.run();
return true;
}
+
@GuardedBy("mLock")
private StatusReport prepareStatusReportLocked() {
- Throwable[] errors = new Throwable[]{
- mRequestQueues[0].mLastError,
- mRequestQueues[1].mLastError,
- mRequestQueues[2].mLastError
- };
+ Throwable[] errors =
+ new Throwable[] {
+ mRequestQueues[0].mLastError,
+ mRequestQueues[1].mLastError,
+ mRequestQueues[2].mLastError
+ };
return new StatusReport(
getStatusForLocked(RequestType.INITIAL),
getStatusForLocked(RequestType.BEFORE),
getStatusForLocked(RequestType.AFTER),
- errors
- );
+ errors);
}
+
@GuardedBy("mLock")
private Status getStatusForLocked(RequestType type) {
return mRequestQueues[type.ordinal()].mStatus;
}
+
@AnyThread
@VisibleForTesting
void recordResult(@NonNull RequestWrapper wrapper, @Nullable Throwable throwable) {
@@ -219,6 +228,7 @@ public class PagingRequestHelper {
dispatchReport(report);
}
}
+
private void dispatchReport(StatusReport report) {
for (Listener listener : mListeners) {
listener.onStatusChange(report);
@@ -246,39 +256,42 @@ public class PagingRequestHelper {
}
return retried;
}
+
static class RequestWrapper implements Runnable {
- @NonNull
- final Request mRequest;
- @NonNull
- final PagingRequestHelper mHelper;
- @NonNull
- final RequestType mType;
- RequestWrapper(@NonNull Request request, @NonNull PagingRequestHelper helper,
+ @NonNull final Request mRequest;
+ @NonNull final PagingRequestHelper mHelper;
+ @NonNull final RequestType mType;
+
+ RequestWrapper(
+ @NonNull Request request,
+ @NonNull PagingRequestHelper helper,
@NonNull RequestType type) {
mRequest = request;
mHelper = helper;
mType = type;
}
+
@Override
public void run() {
mRequest.run(new Request.Callback(this, mHelper));
}
+
void retry(Executor service) {
- service.execute(new Runnable() {
- @Override
- public void run() {
- mHelper.runIfNotRunning(mType, mRequest);
- }
- });
+ service.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ mHelper.runIfNotRunning(mType, mRequest);
+ }
+ });
}
}
/**
* Runner class that runs a request tracked by the {@link PagingRequestHelper}.
- * <p>
- * When a request is invoked, it must call one of {@link Callback#recordFailure(Throwable)}
- * or {@link Callback#recordSuccess()} once and only once. This call
- * can be made any time. Until that method call is made, {@link PagingRequestHelper} will
- * consider the request is running.
+ *
+ * <p>When a request is invoked, it must call one of {@link Callback#recordFailure(Throwable)}
+ * or {@link Callback#recordSuccess()} once and only once. This call can be made any time. Until
+ * that method call is made, {@link PagingRequestHelper} will consider the request is running.
*/
@FunctionalInterface
public interface Request {
@@ -289,20 +302,17 @@ public class PagingRequestHelper {
* @param callback The callback that should be invoked with the result.
*/
void run(Callback callback);
- /**
- * Callback class provided to the {@link #run(Callback)} method to report the result.
- */
+ /** Callback class provided to the {@link #run(Callback)} method to report the result. */
class Callback {
private final AtomicBoolean mCalled = new AtomicBoolean();
private final RequestWrapper mWrapper;
private final PagingRequestHelper mHelper;
+
Callback(RequestWrapper wrapper, PagingRequestHelper helper) {
mWrapper = wrapper;
mHelper = helper;
}
- /**
- * Call this method when the request succeeds and new data is fetched.
- */
+ /** Call this method when the request succeeds and new data is fetched. */
@SuppressWarnings("unused")
public final void recordSuccess() {
if (mCalled.compareAndSet(false, true)) {
@@ -313,8 +323,8 @@ public class PagingRequestHelper {
}
}
/**
- * Call this method with the failure message and the request can be retried via
- * {@link #retryAllFailed()}.
+ * Call this method with the failure message and the request can be retried via {@link
+ * #retryAllFailed()}.
*
* @param throwable The error that occured while carrying out the request.
*/
@@ -322,8 +332,9 @@ public class PagingRequestHelper {
public final void recordFailure(@NonNull Throwable throwable) {
//noinspection ConstantConditions
if (throwable == null) {
- throw new IllegalArgumentException("You must provide a throwable describing"
- + " the error to record the failure");
+ throw new IllegalArgumentException(
+ "You must provide a throwable describing"
+ + " the error to record the failure");
}
if (mCalled.compareAndSet(false, true)) {
mHelper.recordResult(mWrapper, throwable);
@@ -335,28 +346,23 @@ public class PagingRequestHelper {
}
}
/**
- * Data class that holds the information about the current status of the ongoing requests
- * using this helper.
+ * Data class that holds the information about the current status of the ongoing requests using
+ * this helper.
*/
public static final class StatusReport {
- /**
- * Status of the latest request that were submitted with {@link RequestType#INITIAL}.
- */
- @NonNull
- public final Status initial;
- /**
- * Status of the latest request that were submitted with {@link RequestType#BEFORE}.
- */
- @NonNull
- public final Status before;
- /**
- * Status of the latest request that were submitted with {@link RequestType#AFTER}.
- */
- @NonNull
- public final Status after;
- @NonNull
- private final Throwable[] mErrors;
- StatusReport(@NonNull Status initial, @NonNull Status before, @NonNull Status after,
+ /** Status of the latest request that were submitted with {@link RequestType#INITIAL}. */
+ @NonNull public final Status initial;
+ /** Status of the latest request that were submitted with {@link RequestType#BEFORE}. */
+ @NonNull public final Status before;
+ /** Status of the latest request that were submitted with {@link RequestType#AFTER}. */
+ @NonNull public final Status after;
+
+ @NonNull private final Throwable[] mErrors;
+
+ StatusReport(
+ @NonNull Status initial,
+ @NonNull Status before,
+ @NonNull Status after,
@NonNull Throwable[] errors) {
this.initial = initial;
this.before = before;
@@ -369,9 +375,7 @@ public class PagingRequestHelper {
* @return True if there are any running requests, false otherwise.
*/
public boolean hasRunning() {
- return initial == Status.RUNNING
- || before == Status.RUNNING
- || after == Status.RUNNING;
+ return initial == Status.RUNNING || before == Status.RUNNING || after == Status.RUNNING;
}
/**
* Convenience method to check if there are any requests that resulted in an error.
@@ -379,30 +383,34 @@ public class PagingRequestHelper {
* @return True if there are any requests that finished with error, false otherwise.
*/
public boolean hasError() {
- return initial == Status.FAILED
- || before == Status.FAILED
- || after == Status.FAILED;
+ return initial == Status.FAILED || before == Status.FAILED || after == Status.FAILED;
}
/**
* Returns the error for the given request type.
*
* @param type The request type for which the error should be returned.
* @return The {@link Throwable} returned by the failing request with the given type or
- * {@code null} if the request for the given type did not fail.
+ * {@code null} if the request for the given type did not fail.
*/
@Nullable
public Throwable getErrorFor(@NonNull RequestType type) {
return mErrors[type.ordinal()];
}
+
@Override
public String toString() {
return "StatusReport{"
- + "initial=" + initial
- + ", before=" + before
- + ", after=" + after
- + ", mErrors=" + Arrays.toString(mErrors)
+ + "initial="
+ + initial
+ + ", before="
+ + before
+ + ", after="
+ + after
+ + ", mErrors="
+ + Arrays.toString(mErrors)
+ '}';
}
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -414,6 +422,7 @@ public class PagingRequestHelper {
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(mErrors, that.mErrors);
}
+
@Override
public int hashCode() {
int result = initial.hashCode();
@@ -423,9 +432,7 @@ public class PagingRequestHelper {
return result;
}
}
- /**
- * Listener interface to get notified by request status changes.
- */
+ /** Listener interface to get notified by request status changes. */
public interface Listener {
/**
* Called when the status for any of the requests has changed.
@@ -434,58 +441,45 @@ public class PagingRequestHelper {
*/
void onStatusChange(@NonNull StatusReport report);
}
- /**
- * Represents the status of a Request for each {@link RequestType}.
- */
+ /** Represents the status of a Request for each {@link RequestType}. */
public enum Status {
- /**
- * There is current a running request.
- */
+ /** There is current a running request. */
RUNNING,
- /**
- * The last request has succeeded or no such requests have ever been run.
- */
+ /** The last request has succeeded or no such requests have ever been run. */
SUCCESS,
- /**
- * The last request has failed.
- */
+ /** The last request has failed. */
FAILED
}
- /**
- * Available request types.
- */
+ /** Available request types. */
public enum RequestType {
/**
- * Corresponds to an initial request made to a {@link androidx.paging.DataSource} or the empty state for
- * a {@link androidx.paging.PagedList.BoundaryCallback BoundaryCallback}.
+ * Corresponds to an initial request made to a {@link androidx.paging.DataSource} or the
+ * empty state for a {@link androidx.paging.PagedList.BoundaryCallback BoundaryCallback}.
*/
INITIAL,
/**
* Corresponds to the {@code loadBefore} calls in {@link androidx.paging.DataSource} or
- * {@code onItemAtFrontLoaded} in
- * {@link androidx.paging.PagedList.BoundaryCallback BoundaryCallback}.
+ * {@code onItemAtFrontLoaded} in {@link androidx.paging.PagedList.BoundaryCallback
+ * BoundaryCallback}.
*/
BEFORE,
/**
* Corresponds to the {@code loadAfter} calls in {@link androidx.paging.DataSource} or
- * {@code onItemAtEndLoaded} in
- * {@link androidx.paging.PagedList.BoundaryCallback BoundaryCallback}.
+ * {@code onItemAtEndLoaded} in {@link androidx.paging.PagedList.BoundaryCallback
+ * BoundaryCallback}.
*/
AFTER
}
+
class RequestQueue {
- @NonNull
- final RequestType mRequestType;
- @Nullable
- RequestWrapper mFailed;
- @Nullable
- Request mRunning;
- @Nullable
- Throwable mLastError;
- @NonNull
- Status mStatus = Status.SUCCESS;
+ @NonNull final RequestType mRequestType;
+ @Nullable RequestWrapper mFailed;
+ @Nullable Request mRunning;
+ @Nullable Throwable mLastError;
+ @NonNull Status mStatus = Status.SUCCESS;
+
RequestQueue(@NonNull RequestType requestType) {
mRequestType = requestType;
}
}
-} \ No newline at end of file
+}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/PairedList.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/PairedList.java
index a0880a5..230b1cb 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/PairedList.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/PairedList.java
@@ -2,24 +2,22 @@ package com.keylesspalace.tusky.util;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
-
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
-
/**
* This list implementation can help to keep two lists in sync - like real models and view models.
* Every operation on the main list triggers update of the supplementary list (but not vice versa).
- * This makes sure that the main list is always the source of truth.
- * Main list is projected to the supplementary list by the passed mapper function.
- * Paired list is newer actually exposed and clients are provided with {@code getPairedCopy()},
- * {@code getPairedItem()} and {@code setPairedItem()}. This prevents modifications of the
- * supplementary list size so lists are always have the same length.
- * This implementation will not try to recover from exceptional cases so lists may be out of sync
- * after the exception.
+ * This makes sure that the main list is always the source of truth. Main list is projected to the
+ * supplementary list by the passed mapper function. Paired list is newer actually exposed and
+ * clients are provided with {@code getPairedCopy()}, {@code getPairedItem()} and {@code
+ * setPairedItem()}. This prevents modifications of the supplementary list size so lists are always
+ * have the same length. This implementation will not try to recover from exceptional cases so lists
+ * may be out of sync after the exception.
+ *
+ * <p>It is most useful with immutable data because we cannot track changes inside stored objects.
*
- * It is most useful with immutable data because we cannot track changes inside stored objects.
* @param <T> type of elements in the main list
* @param <V> type of elements in supplementary list
*/
@@ -30,8 +28,9 @@ public final class PairedList<T, V> extends AbstractList<T> {
/**
* Construct new paired list. Main and supplementary lists will be empty.
+ *
* @param mapper Function, which will be used to translate items from the main list to the
- * supplementary one.
+ * supplementary one.
*/
public PairedList(Function<T, ? extends V> mapper) {
this.mapper = mapper;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java
index 0d30b14..125d539 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java
@@ -1,5 +1,7 @@
package com.keylesspalace.tusky.util;
+import static org.koin.java.KoinJavaComponent.inject;
+
import android.content.Context;
import android.net.Uri;
import android.util.Log;
@@ -11,7 +13,6 @@ import com.keylesspalace.tusky.db.TootDao;
import com.keylesspalace.tusky.db.TootEntity;
import java.util.ArrayList;
import kotlin.Lazy;
-import static org.koin.java.KoinJavaComponent.inject;
public final class SaveTootHelper {
@@ -28,7 +29,7 @@ public final class SaveTootHelper {
public void deleteDraft(int tootId) {
TootEntity item = tootDao.find(tootId);
- if(item != null) {
+ if (item != null) {
deleteDraft(item);
}
}
@@ -36,12 +37,12 @@ public final class SaveTootHelper {
public void deleteDraft(@NonNull TootEntity item) {
// Delete any media files associated with the status.
ArrayList<String> uris =
- gson.getValue().fromJson(item.getUrls(), new TypeToken<ArrayList<String>>() {
- }.getType());
- if(uris != null) {
- for(String uriString : uris) {
+ gson.getValue()
+ .fromJson(item.getUrls(), new TypeToken<ArrayList<String>>() {}.getType());
+ if (uris != null) {
+ for (String uriString : uris) {
Uri uri = Uri.parse(uriString);
- if(context.getContentResolver().delete(uri, null, null) == 0) {
+ if (context.getContentResolver().delete(uri, null, null) == 0) {
Log.e(TAG, String.format("Did not delete file %s.", uriString));
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
index 8c04a7d..f8100eb 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
@@ -21,7 +21,6 @@ import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
-
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
@@ -52,7 +51,7 @@ public class ThemeUtils {
}
public static int getDimension(@NonNull Context context, @AttrRes int attribute) {
- TypedArray array = context.obtainStyledAttributes(new int[] { attribute });
+ TypedArray array = context.obtainStyledAttributes(new int[] {attribute});
int dimen = array.getDimensionPixelSize(0, -1);
array.recycle();
return dimen;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/TimestampUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/TimestampUtils.java
index c94b422..ce07f1a 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/TimestampUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/TimestampUtils.java
@@ -16,7 +16,6 @@
package com.keylesspalace.tusky.util;
import android.content.Context;
-
import com.keylesspalace.tusky.R;
public class TimestampUtils {
@@ -101,5 +100,4 @@ public class TimestampUtils {
}
return context.getResources().getQuantityString(format, (int) span, (int) span);
}
-
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
index ef4801a..f57d286 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
@@ -16,7 +16,6 @@
package com.keylesspalace.tusky.util;
import androidx.annotation.NonNull;
-
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -40,7 +39,7 @@ public class VersionUtils {
}
public boolean supportsScheduledToots() {
- return (major == 2) ? ( (minor == 7) ? (patch >= 0) : (minor > 7) ) : (major > 2);
+ return (major == 2) ? ((minor == 7) ? (patch >= 0) : (minor > 7)) : (major > 2);
}
public boolean isPleroma() {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
index 95c630b..fd3c37d 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
@@ -22,24 +22,23 @@ package com.keylesspalace.tusky.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
-import com.keylesspalace.tusky.entity.Notification;
-import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.Chat;
import com.keylesspalace.tusky.entity.ChatMessage;
+import com.keylesspalace.tusky.entity.Notification;
+import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.viewdata.ChatMessageViewData;
+import com.keylesspalace.tusky.viewdata.ChatViewData;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import com.keylesspalace.tusky.viewdata.ChatViewData;
-import com.keylesspalace.tusky.viewdata.ChatMessageViewData;
public final class ViewDataUtils {
@Nullable
- public static StatusViewData.Concrete statusToViewData(@Nullable Status status,
- boolean alwaysShowSensitiveMedia,
- boolean alwaysOpenSpoiler) {
+ public static StatusViewData.Concrete statusToViewData(
+ @Nullable Status status, boolean alwaysShowSensitiveMedia, boolean alwaysOpenSpoiler) {
if (status == null) return null;
Status visibleStatus = status.getReblog() == null ? status : status.getReblog();
- return new StatusViewData.Builder().setId(status.getId())
+ return new StatusViewData.Builder()
+ .setId(status.getId())
.setAttachments(visibleStatus.getAttachments())
.setAvatar(visibleStatus.getAccount().getAvatar())
.setContent(visibleStatus.getContent())
@@ -56,11 +55,14 @@ public final class ViewDataUtils {
.setIsShowingSensitiveContent(false)
.setMentions(visibleStatus.getMentions())
.setNickname(visibleStatus.getAccount().getUsername())
- .setRebloggedAvatar(status.getReblog() == null ? null : status.getAccount().getAvatar())
+ .setRebloggedAvatar(
+ status.getReblog() == null ? null : status.getAccount().getAvatar())
.setSensitive(visibleStatus.getSensitive())
- .setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.getSensitive())
+ .setIsShowingSensitiveContent(
+ alwaysShowSensitiveMedia || !visibleStatus.getSensitive())
.setSpoilerText(visibleStatus.getSpoilerText())
- .setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getDisplayName())
+ .setRebloggedByUsername(
+ status.getReblog() == null ? null : status.getAccount().getDisplayName())
.setUserFullName(visibleStatus.getAccount().getName())
.setVisibility(visibleStatus.getVisibility())
.setSenderId(visibleStatus.getAccount().getId())
@@ -68,8 +70,10 @@ public final class ViewDataUtils {
.setApplication(visibleStatus.getApplication())
.setStatusEmojis(visibleStatus.getEmojis())
.setAccountEmojis(visibleStatus.getAccount().getEmojis())
- .setRebloggedByEmojis(status.getReblog() == null ? null : status.getAccount().getEmojis())
- .setCollapsible(SmartLengthInputFilterKt.shouldTrimStatus(visibleStatus.getContent()))
+ .setRebloggedByEmojis(
+ status.getReblog() == null ? null : status.getAccount().getEmojis())
+ .setCollapsible(
+ SmartLengthInputFilterKt.shouldTrimStatus(visibleStatus.getContent()))
.setCollapsed(true)
.setPoll(visibleStatus.getPoll())
.setCard(visibleStatus.getCard())
@@ -83,26 +87,21 @@ public final class ViewDataUtils {
.createStatusViewData();
}
- public static NotificationViewData.Concrete notificationToViewData(Notification notification,
- boolean alwaysShowSensitiveData,
- boolean alwaysOpenSpoiler) {
+ public static NotificationViewData.Concrete notificationToViewData(
+ Notification notification, boolean alwaysShowSensitiveData, boolean alwaysOpenSpoiler) {
return new NotificationViewData.Concrete(
notification.getType(),
notification.getId(),
notification.getAccount(),
statusToViewData(
- notification.getStatus(),
- alwaysShowSensitiveData,
- alwaysOpenSpoiler
- ),
+ notification.getStatus(), alwaysShowSensitiveData, alwaysOpenSpoiler),
notification.getEmoji(),
notification.getEmojiUrl(),
- notification.getTarget()
- );
+ notification.getTarget());
}
public static ChatMessageViewData.Concrete chatMessageToViewData(@Nullable ChatMessage msg) {
- if(msg == null) return null;
+ if (msg == null) return null;
return new ChatMessageViewData.Concrete(
msg.getId(),
@@ -112,8 +111,7 @@ public final class ViewDataUtils {
msg.getCreatedAt(),
msg.getAttachment(),
msg.getEmojis(),
- msg.getCard()
- );
+ msg.getCard());
}
@NonNull
@@ -122,10 +120,7 @@ public final class ViewDataUtils {
chat.getAccount(),
chat.getId(),
chat.getUnread(),
- chatMessageToViewData(
- chat.getLastMessage()
- ),
- chat.getUpdatedAt()
- );
+ chatMessageToViewData(chat.getLastMessage()),
+ chat.getUpdatedAt());
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewPager2Fix.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewPager2Fix.java
index 4698f44..3e92e13 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewPager2Fix.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewPager2Fix.java
@@ -1,40 +1,35 @@
package com.keylesspalace.tusky.util;
-import androidx.viewpager2.widget.ViewPager2;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
import java.lang.reflect.*;
-import java.lang.*;
-/**
- * ViewPager2 written by monkeys!
- */
+/** ViewPager2 written by monkeys! */
public class ViewPager2Fix {
- /**
- * Thanks to @al.e.shevelev@medium.com for solution
- */
- public static Field getViewPagerRecyclerViewField() throws NoSuchFieldException {
- Field f = ViewPager2.class.getDeclaredField("mRecyclerView");
- f.setAccessible(true);
- return f;
- }
-
- public static Field getRecyclerViewTouchSlopField() throws NoSuchFieldException {
- Field f = RecyclerView.class.getDeclaredField("mTouchSlop");
- f.setAccessible(true);
- return f;
- }
-
- public static void reduceVelocity(ViewPager2 pager, float val) {
- try {
- Field recyclerViewField = getViewPagerRecyclerViewField();
- Field touchSlopField = getRecyclerViewTouchSlopField();
+ /** Thanks to @al.e.shevelev@medium.com for solution */
+ public static Field getViewPagerRecyclerViewField() throws NoSuchFieldException {
+ Field f = ViewPager2.class.getDeclaredField("mRecyclerView");
+ f.setAccessible(true);
+ return f;
+ }
+
+ public static Field getRecyclerViewTouchSlopField() throws NoSuchFieldException {
+ Field f = RecyclerView.class.getDeclaredField("mTouchSlop");
+ f.setAccessible(true);
+ return f;
+ }
+
+ public static void reduceVelocity(ViewPager2 pager, float val) {
+ try {
+ Field recyclerViewField = getViewPagerRecyclerViewField();
+ Field touchSlopField = getRecyclerViewTouchSlopField();
- RecyclerView recyclerView = (RecyclerView)recyclerViewField.get(pager);
- int touchSlop = (int)touchSlopField.get(recyclerView);
- touchSlopField.setInt(recyclerView, (int)(touchSlop*val));
- } catch(Exception e) {
- // all possible exceptions must be caught during tests
- ;
- }
- }
+ RecyclerView recyclerView = (RecyclerView) recyclerViewField.get(pager);
+ int touchSlop = (int) touchSlopField.get(recyclerView);
+ touchSlopField.setInt(recyclerView, (int) (touchSlop * val));
+ } catch (Exception e) {
+ // all possible exceptions must be caught during tests
+ ;
+ }
+ }
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java b/husky/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java
index c31b37e..2a57a8e 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java
@@ -21,10 +21,7 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
-/**
- * override BezelImageView from MaterialDrawer library to provide custom outline
- */
-
+/** override BezelImageView from MaterialDrawer library to provide custom outline */
public class BezelImageView extends com.mikepenz.materialdrawer.view.BezelImageView {
public BezelImageView(Context context) {
this(context, null);
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/EmojiKeyboard.java b/husky/app/src/main/java/com/keylesspalace/tusky/view/EmojiKeyboard.java
index 501e02c..358a887 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/view/EmojiKeyboard.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/EmojiKeyboard.java
@@ -1,23 +1,21 @@
package com.keylesspalace.tusky.view;
-import android.view.*;
+import android.app.*;
import android.content.*;
+import android.text.*;
import android.util.*;
+import android.view.*;
import android.widget.*;
-import android.app.*;
-import android.text.*;
-import com.google.android.material.tabs.TabLayout;
-import com.google.android.material.tabs.TabLayoutMediator;
-
import androidx.annotation.NonNull;
-import androidx.viewpager2.widget.ViewPager2;
-import androidx.recyclerview.widget.RecyclerView;
import androidx.preference.PreferenceManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.StickerAdapter;
import com.keylesspalace.tusky.adapter.UnicodeEmojiAdapter;
import com.keylesspalace.tusky.entity.StickerPack;
-
import java.util.*;
public class EmojiKeyboard extends LinearLayout {
@@ -49,14 +47,14 @@ public class EmojiKeyboard extends LinearLayout {
void init(Context context) {
inflate(context, R.layout.item_emoji_picker, this);
-
+
pref = PreferenceManager.getDefaultSharedPreferences(context);
tabs = findViewById(R.id.picker_tabs);
pager = findViewById(R.id.picker_pager);
}
-
+
public static final int UNICODE_MODE = 0;
- public static final int CUSTOM_MODE = 1;
+ public static final int CUSTOM_MODE = 1;
public static final int STICKER_MODE = 2;
private void setupKeyboardWithAdapter(RecyclerView.Adapter adapter, String preferenceKey) {
@@ -65,89 +63,100 @@ public class EmojiKeyboard extends LinearLayout {
List<String> list = Arrays.asList(pref.getString(preferenceKey, "").split(RECENTS_DELIM));
recents = new LinkedHashSet<String>(list);
- ((EmojiKeyboardAdapter)adapter).onRecentsUpdate(recents);
+ ((EmojiKeyboardAdapter) adapter).onRecentsUpdate(recents);
pager.setAdapter(adapter);
- if(currentMediator != null)
- currentMediator.detach();
+ if (currentMediator != null) currentMediator.detach();
- currentMediator = new TabLayoutMediator(tabs, pager, (TabLayoutMediator.TabConfigurationStrategy)adapter);
+ currentMediator =
+ new TabLayoutMediator(
+ tabs, pager, (TabLayoutMediator.TabConfigurationStrategy) adapter);
currentMediator.attach();
}
public void setupStickerKeyboard(OnEmojiSelectedListener listener, StickerPack packs[]) {
MAX_RECENTS_ITEMS = 20;
- setupKeyboardWithAdapter(new StickerAdapter(packs, (_id, _emoji) -> {
- this.appendToRecents(_emoji);
- listener.onEmojiSelected(_id, _emoji);
- }), "STICKER_RECENTS");
+ setupKeyboardWithAdapter(
+ new StickerAdapter(
+ packs,
+ (_id, _emoji) -> {
+ this.appendToRecents(_emoji);
+ listener.onEmojiSelected(_id, _emoji);
+ }),
+ "STICKER_RECENTS");
}
public void setupKeyboard(String id, int mode, OnEmojiSelectedListener listener) {
- switch(mode) {
- // WOOOPS, I forgot that I need to pass data to adapter
- // For stickers, use SetupStickerKeyboard instead
- // For custom emoji, use TODO
+ switch (mode) {
+ // WOOOPS, I forgot that I need to pass data to adapter
+ // For stickers, use SetupStickerKeyboard instead
+ // For custom emoji, use TODO
case CUSTOM_MODE:
case STICKER_MODE:
throw new IllegalArgumentException();
default:
case UNICODE_MODE:
- setupKeyboardWithAdapter(new UnicodeEmojiAdapter(id, (_id, _emoji) -> {
- this.appendToRecents(_emoji);
- listener.onEmojiSelected(_id, _emoji);
- }), "UNICODE_RECENTS");
+ setupKeyboardWithAdapter(
+ new UnicodeEmojiAdapter(
+ id,
+ (_id, _emoji) -> {
+ this.appendToRecents(_emoji);
+ listener.onEmojiSelected(_id, _emoji);
+ }),
+ "UNICODE_RECENTS");
}
}
-
+
private void appendToRecents(String id) {
recents.remove(id);
recents.add(id);
int size = recents.size();
String joined;
final SharedPreferences.Editor editor = pref.edit();
- if(size > MAX_RECENTS_ITEMS) {
+ if (size > MAX_RECENTS_ITEMS) {
List<String> list = new ArrayList<String>(recents);
list = list.subList(size - MAX_RECENTS_ITEMS, size);
joined = TextUtils.join(RECENTS_DELIM, list);
- if(isSticky) {
+ if (isSticky) {
recents = new LinkedHashSet<String>(list);
}
} else {
joined = TextUtils.join(RECENTS_DELIM, recents);
}
-
+
editor.putString(preferenceKey, joined);
editor.apply();
-
+
// no point on updating view if we are will be closed
- if(isSticky) {
- ((EmojiKeyboardAdapter)adapter).onRecentsUpdate(recents);
+ if (isSticky) {
+ ((EmojiKeyboardAdapter) adapter).onRecentsUpdate(recents);
}
}
public interface OnEmojiSelectedListener {
void onEmojiSelected(@NonNull String id, @NonNull String emoji);
}
-
+
public interface EmojiKeyboardAdapter {
void onRecentsUpdate(@NonNull Set<String> set);
}
-
+
public static void show(Context ctx, String id, int mode, OnEmojiSelectedListener listener) {
final Dialog dialog = new Dialog(ctx);
-
+
dialog.setTitle(null);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_emoji_keyboard);
- EmojiKeyboard kbd = (EmojiKeyboard)dialog.findViewById(R.id.dialog_emoji_keyboard);
- kbd.setupKeyboard(id, mode, (_id, _emoji) -> {
- listener.onEmojiSelected(_id, _emoji);
- if(!kbd.isSticky)
- dialog.dismiss();
- });
-
+ EmojiKeyboard kbd = (EmojiKeyboard) dialog.findViewById(R.id.dialog_emoji_keyboard);
+ kbd.setupKeyboard(
+ id,
+ mode,
+ (_id, _emoji) -> {
+ listener.onEmojiSelected(_id, _emoji);
+ if (!kbd.isSticky) dialog.dismiss();
+ });
+
dialog.show();
}
}
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java b/husky/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java
index 50f9ea6..31f039c 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java
@@ -35,7 +35,6 @@ public abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListe
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
if (totalItemCount < previousTotalItemCount) {
previousTotalItemCount = totalItemCount;
-
}
if (totalItemCount != previousTotalItemCount) {
previousTotalItemCount = totalItemCount;
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
index 485d7f7..aa4466c 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
@@ -22,23 +22,19 @@ package com.keylesspalace.tusky.viewdata;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Notification;
-
-import java.util.Objects;
-
import io.reactivex.annotations.Nullable;
+import java.util.Objects;
/**
- * Class to represent data required to display either a notification or a placeholder.
- * It is either a {@link Placeholder} or a {@link Concrete}.
- * It is modelled this way because close relationship between placeholder and concrete notification
- * is fine in this case. Placeholder case is not modelled as a type of notification because
- * invariants would be violated and because it would model domain incorrectly. It is prefereable to
- * {@link com.keylesspalace.tusky.util.Either} because class hierarchy is cheaper, faster and
- * more native.
+ * Class to represent data required to display either a notification or a placeholder. It is either
+ * a {@link Placeholder} or a {@link Concrete}. It is modelled this way because close relationship
+ * between placeholder and concrete notification is fine in this case. Placeholder case is not
+ * modelled as a type of notification because invariants would be violated and because it would
+ * model domain incorrectly. It is prefereable to {@link com.keylesspalace.tusky.util.Either}
+ * because class hierarchy is cheaper, faster and more native.
*/
public abstract class NotificationViewData {
- private NotificationViewData() {
- }
+ private NotificationViewData() {}
public abstract long getViewDataId();
@@ -48,19 +44,19 @@ public abstract class NotificationViewData {
private final Notification.Type type;
private final String id;
private final Account account;
- @Nullable
- private final StatusViewData.Concrete statusViewData;
- @Nullable
- private final String emoji;
- @Nullable
- private final String emojiUrl;
- @Nullable
- private final Account target; // move notification
-
- public Concrete(Notification.Type type, String id, Account account,
- @Nullable StatusViewData.Concrete statusViewData,
- @Nullable String emoji, @Nullable String emojiUrl,
- @Nullable Account target) {
+ @Nullable private final StatusViewData.Concrete statusViewData;
+ @Nullable private final String emoji;
+ @Nullable private final String emojiUrl;
+ @Nullable private final Account target; // move notification
+
+ public Concrete(
+ Notification.Type type,
+ String id,
+ Account account,
+ @Nullable StatusViewData.Concrete statusViewData,
+ @Nullable String emoji,
+ @Nullable String emojiUrl,
+ @Nullable Account target) {
this.type = type;
this.id = id;
this.account = account;
@@ -89,7 +85,7 @@ public abstract class NotificationViewData {
@Nullable
public String getEmoji() {
- return emoji;
+ return emoji;
}
@Nullable
@@ -112,15 +108,19 @@ public abstract class NotificationViewData {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Concrete concrete = (Concrete) o;
- return type == concrete.type &&
- Objects.equals(id, concrete.id) &&
- account.getId().equals(concrete.account.getId()) &&
- (emoji != null && concrete.emoji != null && emoji.equals(concrete.emoji)) &&
- (emojiUrl != null && concrete.emojiUrl != null && emojiUrl.equals(concrete.emojiUrl)) &&
- (target != null && concrete.target != null && target.getId().equals(concrete.target.getId())) &&
- (statusViewData == concrete.statusViewData ||
- statusViewData != null &&
- statusViewData.deepEquals(concrete.statusViewData));
+ return type == concrete.type
+ && Objects.equals(id, concrete.id)
+ && account.getId().equals(concrete.account.getId())
+ && (emoji != null && concrete.emoji != null && emoji.equals(concrete.emoji))
+ && (emojiUrl != null
+ && concrete.emojiUrl != null
+ && emojiUrl.equals(concrete.emojiUrl))
+ && (target != null
+ && concrete.target != null
+ && target.getId().equals(concrete.target.getId()))
+ && (statusViewData == concrete.statusViewData
+ || statusViewData != null
+ && statusViewData.deepEquals(concrete.statusViewData));
}
@Override
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java
index 0238a82..5cc666d 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java
@@ -38,13 +38,12 @@ import java.util.List;
import java.util.Objects;
/**
- * Class to represent data required to display either a notification or a placeholder.
- * It is either a {@link StatusViewData.Concrete} or a {@link StatusViewData.Placeholder}.
+ * Class to represent data required to display either a notification or a placeholder. It is either
+ * a {@link StatusViewData.Concrete} or a {@link StatusViewData.Placeholder}.
*/
public abstract class StatusViewData {
- private StatusViewData() {
- }
+ private StatusViewData() {}
public abstract long getViewDataId();
@@ -59,14 +58,11 @@ public abstract class StatusViewData {
final boolean reblogged;
final boolean favourited;
final boolean bookmarked;
- @Nullable
- private final String spoilerText;
+ @Nullable private final String spoilerText;
private final Status.Visibility visibility;
private final List<Attachment> attachments;
- @Nullable
- private final String rebloggedByUsername;
- @Nullable
- private final String rebloggedAvatar;
+ @Nullable private final String rebloggedByUsername;
+ @Nullable private final String rebloggedAvatar;
private final boolean isSensitive;
final boolean isExpanded;
private final boolean isShowingContent;
@@ -77,55 +73,81 @@ public abstract class StatusViewData {
private final Date editedAt;
private final int reblogsCount;
private final int favouritesCount;
- @Nullable
- private final String inReplyToId;
- @Nullable
- private final String inReplyToAccountAcct;
+ @Nullable private final String inReplyToId;
+ @Nullable private final String inReplyToAccountAcct;
// I would rather have something else but it would be too much of a rewrite
- @Nullable
- private final Status.Mention[] mentions;
+ @Nullable private final Status.Mention[] mentions;
private final String senderId;
private final boolean rebloggingEnabled;
private final Status.Application application;
private final List<Emoji> statusEmojis;
private final List<Emoji> accountEmojis;
private final List<Emoji> rebloggedByAccountEmojis;
- @Nullable
- private final Card card;
+ @Nullable private final Card card;
private final boolean isCollapsible;
- /**
- * Whether the status meets the requirement to be collapse
- */
+ /** Whether the status meets the requirement to be collapse */
final boolean isCollapsed;
- /**
- * Whether the status is shown partially or fully
- */
- @Nullable
- private final PollViewData poll;
+ /** Whether the status is shown partially or fully */
+ @Nullable private final PollViewData poll;
+
private final boolean isBot;
private final boolean isMuted; /* user toggle */
private final boolean isThreadMuted; /* thread_muted state got from backend */
private final boolean isUserMuted; /* muted state got from backend */
private final String conversationId;
- @Nullable
- private final List<EmojiReaction> emojiReactions;
+ @Nullable private final List<EmojiReaction> emojiReactions;
private final boolean parentVisible;
- public Concrete(String id, Spanned content, boolean reblogged, boolean favourited, boolean bookmarked,
- @Nullable String spoilerText, Status.Visibility visibility, List<Attachment> attachments,
- @Nullable String rebloggedByUsername, @Nullable String rebloggedAvatar, boolean sensitive, boolean isExpanded,
- boolean isShowingContent, String userFullName, String nickname, String avatar,
- Date createdAt, Date editedAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId,
- @Nullable String inReplyToAccountAcct, @Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
- Status.Application application, List<Emoji> statusEmojis, List<Emoji> accountEmojis, List<Emoji> rebloggedByAccountEmojis, @Nullable Card card,
- boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot, boolean isMuted, boolean isThreadMuted,
- boolean isUserMuted, String conversationId, @Nullable List<EmojiReaction> emojiReactions, boolean parentVisible) {
+ public Concrete(
+ String id,
+ Spanned content,
+ boolean reblogged,
+ boolean favourited,
+ boolean bookmarked,
+ @Nullable String spoilerText,
+ Status.Visibility visibility,
+ List<Attachment> attachments,
+ @Nullable String rebloggedByUsername,
+ @Nullable String rebloggedAvatar,
+ boolean sensitive,
+ boolean isExpanded,
+ boolean isShowingContent,
+ String userFullName,
+ String nickname,
+ String avatar,
+ Date createdAt,
+ Date editedAt,
+ int reblogsCount,
+ int favouritesCount,
+ @Nullable String inReplyToId,
+ @Nullable String inReplyToAccountAcct,
+ @Nullable Status.Mention[] mentions,
+ String senderId,
+ boolean rebloggingEnabled,
+ Status.Application application,
+ List<Emoji> statusEmojis,
+ List<Emoji> accountEmojis,
+ List<Emoji> rebloggedByAccountEmojis,
+ @Nullable Card card,
+ boolean isCollapsible,
+ boolean isCollapsed,
+ @Nullable PollViewData poll,
+ boolean isBot,
+ boolean isMuted,
+ boolean isThreadMuted,
+ boolean isUserMuted,
+ String conversationId,
+ @Nullable List<EmojiReaction> emojiReactions,
+ boolean parentVisible) {
this.id = id;
- if(Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
// https://github.com/tuskyapp/Tusky/issues/563
this.content = replaceCrashingCharacters(content);
- this.spoilerText = spoilerText == null ? null : replaceCrashingCharacters(spoilerText).toString();
+ this.spoilerText =
+ spoilerText == null
+ ? null
+ : replaceCrashingCharacters(spoilerText).toString();
this.nickname = replaceCrashingCharacters(nickname).toString();
} else {
this.content = content;
@@ -263,7 +285,7 @@ public abstract class StatusViewData {
}
public String getInReplyToAccountAcct() {
- if(inReplyToAccountAcct != null) {
+ if (inReplyToAccountAcct != null) {
return inReplyToAccountAcct;
}
return "";
@@ -308,8 +330,8 @@ public abstract class StatusViewData {
}
/**
- * Specifies whether the content of this post is allowed to be collapsed or if it should show
- * all content regardless.
+ * Specifies whether the content of this post is allowed to be collapsed or if it should
+ * show all content regardless.
*
* @return Whether the post is collapsible or never collapsed.
*/
@@ -318,8 +340,8 @@ public abstract class StatusViewData {
}
/**
- * Specifies whether the content of this post is currently limited in visibility to the first
- * 500 characters or not.
+ * Specifies whether the content of this post is currently limited in visibility to the
+ * first 500 characters or not.
*
* @return Whether the post is collapsed or fully expanded.
*/
@@ -356,48 +378,48 @@ public abstract class StatusViewData {
}
public boolean deepEquals(StatusViewData o) {
- if(this == o) return true;
- if(o == null || getClass() != o.getClass()) return false;
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
Concrete concrete = (Concrete) o;
- return reblogged == concrete.reblogged &&
- favourited == concrete.favourited &&
- bookmarked == concrete.bookmarked &&
- isSensitive == concrete.isSensitive &&
- isExpanded == concrete.isExpanded &&
- isShowingContent == concrete.isShowingContent &&
- isBot == concrete.isBot &&
- reblogsCount == concrete.reblogsCount &&
- favouritesCount == concrete.favouritesCount &&
- rebloggingEnabled == concrete.rebloggingEnabled &&
- Objects.equals(id, concrete.id) &&
- Objects.equals(content, concrete.content) &&
- Objects.equals(spoilerText, concrete.spoilerText) &&
- visibility == concrete.visibility &&
- Objects.equals(attachments, concrete.attachments) &&
- Objects.equals(rebloggedByUsername, concrete.rebloggedByUsername) &&
- Objects.equals(rebloggedAvatar, concrete.rebloggedAvatar) &&
- Objects.equals(userFullName, concrete.userFullName) &&
- Objects.equals(nickname, concrete.nickname) &&
- Objects.equals(avatar, concrete.avatar) &&
- Objects.equals(createdAt, concrete.createdAt) &&
- Objects.equals(editedAt, concrete.editedAt) &&
- Objects.equals(inReplyToId, concrete.inReplyToId) &&
- Objects.equals(inReplyToAccountAcct, concrete.inReplyToAccountAcct) &&
- Arrays.equals(mentions, concrete.mentions) &&
- Objects.equals(senderId, concrete.senderId) &&
- Objects.equals(application, concrete.application) &&
- Objects.equals(statusEmojis, concrete.statusEmojis) &&
- Objects.equals(accountEmojis, concrete.accountEmojis) &&
- Objects.equals(rebloggedByAccountEmojis, concrete.rebloggedByAccountEmojis) &&
- Objects.equals(card, concrete.card) &&
- Objects.equals(poll, concrete.poll) &&
- isCollapsed == concrete.isCollapsed &&
- isMuted == concrete.isMuted &&
- isThreadMuted == concrete.isThreadMuted &&
- isUserMuted == concrete.isUserMuted &&
- conversationId == concrete.conversationId &&
- Objects.equals(emojiReactions, concrete.emojiReactions) &&
- parentVisible == concrete.parentVisible;
+ return reblogged == concrete.reblogged
+ && favourited == concrete.favourited
+ && bookmarked == concrete.bookmarked
+ && isSensitive == concrete.isSensitive
+ && isExpanded == concrete.isExpanded
+ && isShowingContent == concrete.isShowingContent
+ && isBot == concrete.isBot
+ && reblogsCount == concrete.reblogsCount
+ && favouritesCount == concrete.favouritesCount
+ && rebloggingEnabled == concrete.rebloggingEnabled
+ && Objects.equals(id, concrete.id)
+ && Objects.equals(content, concrete.content)
+ && Objects.equals(spoilerText, concrete.spoilerText)
+ && visibility == concrete.visibility
+ && Objects.equals(attachments, concrete.attachments)
+ && Objects.equals(rebloggedByUsername, concrete.rebloggedByUsername)
+ && Objects.equals(rebloggedAvatar, concrete.rebloggedAvatar)
+ && Objects.equals(userFullName, concrete.userFullName)
+ && Objects.equals(nickname, concrete.nickname)
+ && Objects.equals(avatar, concrete.avatar)
+ && Objects.equals(createdAt, concrete.createdAt)
+ && Objects.equals(editedAt, concrete.editedAt)
+ && Objects.equals(inReplyToId, concrete.inReplyToId)
+ && Objects.equals(inReplyToAccountAcct, concrete.inReplyToAccountAcct)
+ && Arrays.equals(mentions, concrete.mentions)
+ && Objects.equals(senderId, concrete.senderId)
+ && Objects.equals(application, concrete.application)
+ && Objects.equals(statusEmojis, concrete.statusEmojis)
+ && Objects.equals(accountEmojis, concrete.accountEmojis)
+ && Objects.equals(rebloggedByAccountEmojis, concrete.rebloggedByAccountEmojis)
+ && Objects.equals(card, concrete.card)
+ && Objects.equals(poll, concrete.poll)
+ && isCollapsed == concrete.isCollapsed
+ && isMuted == concrete.isMuted
+ && isThreadMuted == concrete.isThreadMuted
+ && isUserMuted == concrete.isUserMuted
+ && conversationId == concrete.conversationId
+ && Objects.equals(emojiReactions, concrete.emojiReactions)
+ && parentVisible == concrete.parentVisible;
}
static Spanned replaceCrashingCharacters(Spanned content) {
@@ -409,17 +431,17 @@ public abstract class StatusViewData {
SpannableStringBuilder builder = null;
int length = content.length();
- for(int index = 0; index < length; ++index) {
+ for (int index = 0; index < length; ++index) {
char character = content.charAt(index);
// If there are more than one or two, switch to a map
- if(character == SOFT_HYPHEN) {
- if(!replacing) {
+ if (character == SOFT_HYPHEN) {
+ if (!replacing) {
replacing = true;
builder = new SpannableStringBuilder(content, 0, index);
}
builder.append(ASCII_HYPHEN);
- } else if(replacing) {
+ } else if (replacing) {
builder.append(character);
}
}
@@ -452,15 +474,15 @@ public abstract class StatusViewData {
@Override
public boolean deepEquals(StatusViewData other) {
- if(!(other instanceof Placeholder)) return false;
+ if (!(other instanceof Placeholder)) return false;
Placeholder that = (Placeholder) other;
return isLoading == that.isLoading && id.equals(that.id);
}
@Override
public boolean equals(Object o) {
- if(this == o) return true;
- if(o == null || getClass() != o.getClass()) return false;
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
Placeholder that = (Placeholder) o;
@@ -507,14 +529,11 @@ public abstract class StatusViewData {
private List<Emoji> rebloggedByAccountEmojis;
private Card card;
private boolean isCollapsible;
- /**
- * Whether the status meets the requirement to be collapsed
- */
+ /** Whether the status meets the requirement to be collapsed */
private boolean isCollapsed;
- /**
- * Whether the status is shown partially or fully
- */
+ /** Whether the status is shown partially or fully */
private PollViewData poll;
+
private boolean isBot;
private boolean isMuted;
private boolean isThreadMuted;
@@ -523,8 +542,7 @@ public abstract class StatusViewData {
private List<EmojiReaction> emojiReactions;
private boolean parentVisible;
- public Builder() {
- }
+ public Builder() {}
public Builder(final StatusViewData.Concrete viewData) {
id = viewData.id;
@@ -534,7 +552,8 @@ public abstract class StatusViewData {
bookmarked = viewData.bookmarked;
spoilerText = viewData.spoilerText;
visibility = viewData.visibility;
- attachments = viewData.attachments == null ? null : new ArrayList<>(viewData.attachments);
+ attachments =
+ viewData.attachments == null ? null : new ArrayList<>(viewData.attachments);
rebloggedByUsername = viewData.rebloggedByUsername;
rebloggedAvatar = viewData.rebloggedAvatar;
isSensitive = viewData.isSensitive;
@@ -728,8 +747,8 @@ public abstract class StatusViewData {
}
/**
- * Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to support collapsing
- * its content limiting the visible length when collapsed at 500 characters,
+ * Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to support
+ * collapsing its content limiting the visible length when collapsed at 500 characters,
*
* @param collapsible Whether the status should support being collapsed or not.
* @return This {@link com.keylesspalace.tusky.viewdata.StatusViewData.Builder} instance.
@@ -740,8 +759,9 @@ public abstract class StatusViewData {
}
/**
- * Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to start in a collapsed
- * state, hiding partially the content of the post if it exceeds a certain amount of characters.
+ * Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to start in a
+ * collapsed state, hiding partially the content of the post if it exceeds a certain amount
+ * of characters.
*
* @param collapsed Whether to show the full content of the status or not.
* @return This {@link com.keylesspalace.tusky.viewdata.StatusViewData.Builder} instance.
@@ -782,16 +802,51 @@ public abstract class StatusViewData {
}
public StatusViewData.Concrete createStatusViewData() {
- if(this.statusEmojis == null) statusEmojis = Collections.emptyList();
- if(this.accountEmojis == null) accountEmojis = Collections.emptyList();
- if(this.createdAt == null) createdAt = new Date();
-
- return new StatusViewData.Concrete(id, content, reblogged, favourited, bookmarked, spoilerText,
- visibility, attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
- isShowingContent, userFullName, nickname, avatar, createdAt, editedAt, reblogsCount,
- favouritesCount, inReplyToId, inReplyToAccountAcct, mentions, senderId, rebloggingEnabled, application,
- statusEmojis, accountEmojis, rebloggedByAccountEmojis, card, isCollapsible, isCollapsed, poll, isBot, isMuted, isThreadMuted,
- isUserMuted, conversationId, emojiReactions, parentVisible);
+ if (this.statusEmojis == null) statusEmojis = Collections.emptyList();
+ if (this.accountEmojis == null) accountEmojis = Collections.emptyList();
+ if (this.createdAt == null) createdAt = new Date();
+
+ return new StatusViewData.Concrete(
+ id,
+ content,
+ reblogged,
+ favourited,
+ bookmarked,
+ spoilerText,
+ visibility,
+ attachments,
+ rebloggedByUsername,
+ rebloggedAvatar,
+ isSensitive,
+ isExpanded,
+ isShowingContent,
+ userFullName,
+ nickname,
+ avatar,
+ createdAt,
+ editedAt,
+ reblogsCount,
+ favouritesCount,
+ inReplyToId,
+ inReplyToAccountAcct,
+ mentions,
+ senderId,
+ rebloggingEnabled,
+ application,
+ statusEmojis,
+ accountEmojis,
+ rebloggedByAccountEmojis,
+ card,
+ isCollapsible,
+ isCollapsed,
+ poll,
+ isBot,
+ isMuted,
+ isThreadMuted,
+ isUserMuted,
+ conversationId,
+ emojiReactions,
+ parentVisible);
}
}
}
diff --git a/husky/app/src/test/java/com/keylesspalace/tusky/util/ViewPager2FixTest.java b/husky/app/src/test/java/com/keylesspalace/tusky/util/ViewPager2FixTest.java
index 859f25c..50edb50 100644
--- a/husky/app/src/test/java/com/keylesspalace/tusky/util/ViewPager2FixTest.java
+++ b/husky/app/src/test/java/com/keylesspalace/tusky/util/ViewPager2FixTest.java
@@ -10,7 +10,7 @@ public class ViewPager2FixTest {
public void getViewPagerRecyclerViewFieldTest() {
try {
Field f = ViewPager2Fix.getViewPagerRecyclerViewField();
- } catch(Exception e) {
+ } catch (Exception e) {
Assert.fail("asdf");
}
}
@@ -19,7 +19,7 @@ public class ViewPager2FixTest {
public void getRecyclerViewTouchSlopFieldTest() {
try {
Field f = ViewPager2Fix.getRecyclerViewTouchSlopField();
- } catch(Exception e) {
+ } catch (Exception e) {
Assert.fail("asdf");
}
}