add support for sending bites
Some checks failed
Nightly builds / build (push) Waiting to run
Mirror to Codeberg / sync-git (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled

This commit is contained in:
notfire 2025-03-19 20:57:31 -04:00
parent ab72435347
commit ab3d3ff068
Signed by: notfire
GPG key ID: 3AFDACAAB4E56B16
14 changed files with 87 additions and 14 deletions

View file

@ -13,11 +13,11 @@ android {
defaultConfig { defaultConfig {
manifestPlaceholders = [oAuthScheme:"moshidon-android-auth"] manifestPlaceholders = [oAuthScheme:"moshidon-android-auth"]
archivesBaseName = "moshidon" archivesBaseName = "moshidon"
applicationId "org.joinmastodon.android.moshinda" applicationId "org.joinmastodon.android.moshinda.luke"
minSdk 23 minSdk 23
targetSdk 34 targetSdk 34
versionCode 108 versionCode 108
versionName "2.3.0+fork.108.moshinda" versionName "2.3.0+fork.108.moshinda.luke"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW'] resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
} }

View file

@ -139,7 +139,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this; return this;
} }
protected void setRequestBody(Object body){ public void setRequestBody(Object body){
requestBody=body; requestBody=body;
} }

View file

@ -0,0 +1,14 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.FollowList;
import java.util.List;
public class BiteAccount extends MastodonAPIRequest{
public BiteAccount(String id){
super(HttpMethod.POST, "/users/"+id+"/bite", BiteAccount.class);
}
}

View file

@ -0,0 +1,9 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
public class BitePost extends MastodonAPIRequest{
public BitePost(String id){
super(HttpMethod.POST, "/statuses/"+id+"/bite", BitePost.class);
}
}

View file

@ -60,6 +60,7 @@ import androidx.viewpager2.widget.ViewPager2;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.BiteAccount;
import org.joinmastodon.android.api.requests.accounts.GetAccountByID; import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
@ -816,7 +817,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(isOwnProfile){ if(isOwnProfile){
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks);
}else{ }else{
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.edit_note); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.edit_note, R.id.biteuser_btn);
} }
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
menu.findItem(R.id.open_with_account).setVisible(hasMultipleAccounts); menu.findItem(R.id.open_with_account).setVisible(hasMultipleAccounts);
@ -933,6 +934,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.save){ }else if(id==R.id.save){
if(isInEditMode) if(isInEditMode)
saveAndExitEditMode(); saveAndExitEditMode();
}else if(id==R.id.biteuser_btn){
new BiteAccount(profileAccountID).setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
Toast.makeText(getContext(), "User was bitten", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
}
}).exec(accountID).setRequestBody(new Object());
}else if(id==R.id.edit_note){ }else if(id==R.id.edit_note){
if(noteWrap.getVisibility()==View.GONE){ if(noteWrap.getVisibility()==View.GONE){
showPrivateNote(); showPrivateNote();

View file

@ -65,7 +65,9 @@ public class Notification extends BaseModel implements DisplayItemsParent{
@SerializedName("admin.sign_up") @SerializedName("admin.sign_up")
SIGN_UP, SIGN_UP,
@SerializedName("admin.report") @SerializedName("admin.report")
REPORT REPORT,
@SerializedName("bite")
BITE
} }
@Parcel @Parcel

View file

@ -111,12 +111,13 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
list.setPadding(V.dp(16), 0, V.dp(16), 0); list.setPadding(V.dp(16), 0, V.dp(16), 0);
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null); imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
if (!forReaction){
List<Emoji> recentEmoji=new ArrayList<>(lp.recentCustomEmoji); List<Emoji> recentEmoji=new ArrayList<>(lp.recentCustomEmoji);
if(!recentEmoji.isEmpty()) if(!recentEmoji.isEmpty())
adapter.addAdapter(new SingleCategoryAdapter(recentEmojiCategory=new EmojiCategory(activity.getString(R.string.mo_emoji_recent), recentEmoji))); adapter.addAdapter(new SingleCategoryAdapter(recentEmojiCategory=new EmojiCategory(activity.getString(R.string.mo_emoji_recent), recentEmoji)));
for(EmojiCategory category : emojis)
for(EmojiCategory category:emojis)
adapter.addAdapter(new SingleCategoryAdapter(category)); adapter.addAdapter(new SingleCategoryAdapter(category));
}
list.setAdapter(adapter); list.setAdapter(adapter);
list.addItemDecoration(new RecyclerView.ItemDecoration(){ list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override @Override
@ -229,6 +230,15 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
} }
} }
public void customToggleKeyboardPopup(){
List<Emoji> recentEmoji=new ArrayList<>(lp.recentCustomEmoji);
if(!recentEmoji.isEmpty())
adapter.addAdapter(new SingleCategoryAdapter(recentEmojiCategory=new EmojiCategory(activity.getString(R.string.mo_emoji_recent), recentEmoji)));
for(EmojiCategory category : emojis)
adapter.addAdapter(new SingleCategoryAdapter(category));
super.toggleKeyboardPopup(null);
}
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter, Filterable{ private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter, Filterable{
private EmojiCategory category; private EmojiCategory category;

View file

@ -317,7 +317,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public void onBackspace() {} public void onBackspace() {}
private void onReactClick(View v){ private void onReactClick(View v){
emojiKeyboard.toggleKeyboardPopup(null); emojiKeyboard.customToggleKeyboardPopup();
v.setSelected(emojiKeyboard.isVisible()); v.setSelected(emojiKeyboard.isVisible());
space.setVisibility(emojiKeyboard.isVisible() ? View.VISIBLE : View.GONE); space.setVisibility(emojiKeyboard.isVisible() ? View.VISIBLE : View.GONE);
DisplayMetrics displayMetrics = new DisplayMetrics(); DisplayMetrics displayMetrics = new DisplayMetrics();

View file

@ -22,9 +22,12 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.BiteAccount;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.announcements.DismissAnnouncement; import org.joinmastodon.android.api.requests.announcements.DismissAnnouncement;
import org.joinmastodon.android.api.requests.statuses.BitePost;
import org.joinmastodon.android.api.requests.statuses.CreateStatus; import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText; import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
@ -263,6 +266,18 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
UiUtils.launchWebBrowser(activity, item.status.url); UiUtils.launchWebBrowser(activity, item.status.url);
}else if(id==R.id.copy_link){ }else if(id==R.id.copy_link){
UiUtils.copyText(parent, item.status.url); UiUtils.copyText(parent, item.status.url);
}else if(id==R.id.bitepost_btn){
new BitePost(item.status.id).setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
Toast.makeText(MastodonApp.context, "Post was bitten", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(MastodonApp.context);
}
}).exec(item.parentFragment.getAccountID()).setRequestBody(new Object());
}else if(id==R.id.follow){ }else if(id==R.id.follow){
if(relationship==null) if(relationship==null)
return true; return true;
@ -513,6 +528,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
menu.findItem(R.id.unmute_conversation).setVisible(item.status!=null && item.status.muted); menu.findItem(R.id.unmute_conversation).setVisible(item.status!=null && item.status.muted);
menu.findItem(R.id.open_in_browser).setVisible(!isPostScheduled && item.status!=null); menu.findItem(R.id.open_in_browser).setVisible(!isPostScheduled && item.status!=null);
menu.findItem(R.id.copy_link).setVisible(!isPostScheduled && item.status!=null); menu.findItem(R.id.copy_link).setVisible(!isPostScheduled && item.status!=null);
menu.findItem(R.id.bitepost_btn).setVisible(!isOwnPost && !isPostScheduled && item.status!=null);
MenuItem blockDomain=menu.findItem(R.id.block_domain); MenuItem blockDomain=menu.findItem(R.id.block_domain);
MenuItem mute=menu.findItem(R.id.mute); MenuItem mute=menu.findItem(R.id.mute);
MenuItem block=menu.findItem(R.id.block); MenuItem block=menu.findItem(R.id.block);

View file

@ -75,6 +75,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
case REPORT -> R.string.sk_reported; case REPORT -> R.string.sk_reported;
case REACTION, PLEROMA_EMOJI_REACTION -> case REACTION, PLEROMA_EMOJI_REACTION ->
!TextUtils.isEmpty(notification.emoji) ? R.string.sk_reacted_with : R.string.sk_reacted; !TextUtils.isEmpty(notification.emoji) ? R.string.sk_reacted_with : R.string.sk_reacted;
case BITE -> R.string.sk_bit_you;
default -> throw new IllegalStateException("Unexpected value: "+notification.type); default -> throw new IllegalStateException("Unexpected value: "+notification.type);
}); });
@ -170,6 +171,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
case SIGN_UP -> R.drawable.ic_fluent_person_available_24_filled; case SIGN_UP -> R.drawable.ic_fluent_person_available_24_filled;
case UPDATE -> R.drawable.ic_fluent_edit_24_filled; case UPDATE -> R.drawable.ic_fluent_edit_24_filled;
case REACTION, PLEROMA_EMOJI_REACTION -> R.drawable.ic_fluent_add_24_filled; case REACTION, PLEROMA_EMOJI_REACTION -> R.drawable.ic_fluent_add_24_filled;
case BITE -> R.drawable.ic_fluent_animal_cat_24_regular;
default -> throw new IllegalStateException("Unexpected value: "+item.notification.type); default -> throw new IllegalStateException("Unexpected value: "+item.notification.type);
}); });
icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){ icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){

View file

@ -3,7 +3,6 @@ package org.joinmastodon.android.ui.viewholders;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
@ -25,6 +24,7 @@ import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.BiteAccount;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.AddAccountToListsFragment; import org.joinmastodon.android.fragments.AddAccountToListsFragment;
@ -66,6 +66,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
private final View checkbox; private final View checkbox;
private final ProgressBar actionProgress; private final ProgressBar actionProgress;
private final ImageButton menuButton; private final ImageButton menuButton;
private final ImageButton biteUserButton;
private final String accountID; private final String accountID;
private final Fragment fragment; private final Fragment fragment;
@ -102,6 +103,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
checkbox=findViewById(R.id.checkbox); checkbox=findViewById(R.id.checkbox);
actionProgress=findViewById(R.id.action_progress); actionProgress=findViewById(R.id.action_progress);
menuButton=findViewById(R.id.options_btn); menuButton=findViewById(R.id.options_btn);
biteUserButton=findViewById(R.id.biteuser_btn);
avatar.setOutlineProvider(OutlineProviders.roundedRect(10)); avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
@ -112,7 +114,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
contextMenu=new PopupMenu(fragment.getActivity(), menuAnchor); contextMenu=new PopupMenu(fragment.getActivity(), menuAnchor);
contextMenu.inflate(R.menu.profile); contextMenu.inflate(R.menu.profile);
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected); contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
menuButton.setOnClickListener(v->showMenuFromButton()); menuButton.setOnClickListener(v -> showMenuFromButton());
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI() && !UiUtils.isMagic()) if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI() && !UiUtils.isMagic())
contextMenu.getMenu().setGroupDividerEnabled(true); contextMenu.getMenu().setGroupDividerEnabled(true);
UiUtils.enablePopupMenuIcons(fragment.getContext(), contextMenu); UiUtils.enablePopupMenuIcons(fragment.getContext(), contextMenu);

View file

@ -8,6 +8,7 @@
<item android:id="@+id/unpin" android:title="@string/sk_unpin_post" android:icon="@drawable/ic_fluent_pin_off_24_regular"/> <item android:id="@+id/unpin" android:title="@string/sk_unpin_post" android:icon="@drawable/ic_fluent_pin_off_24_regular"/>
</group> </group>
<group android:id="@+id/menu_group2"> <group android:id="@+id/menu_group2">
<item android:id="@+id/bitepost_btn" android:title="@string/sk_bite_post" android:icon="@drawable/ic_fluent_animal_cat_24_regular"/>
<item android:id="@+id/manage_user_lists" android:title="@string/add_user_to_list" android:icon="@drawable/ic_fluent_people_24_regular"/> <item android:id="@+id/manage_user_lists" android:title="@string/add_user_to_list" android:icon="@drawable/ic_fluent_people_24_regular"/>
<item android:id="@+id/follow" android:title="@string/follow_user" android:icon="@drawable/ic_fluent_person_add_24_regular"/> <item android:id="@+id/follow" android:title="@string/follow_user" android:icon="@drawable/ic_fluent_person_add_24_regular"/>
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/> <item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>

View file

@ -2,6 +2,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group1"> <group android:id="@+id/menu_group1">
<item android:id="@+id/edit_note" android:title="@string/sk_add_note" android:icon="@drawable/ic_fluent_notepad_24_regular" android:showAsAction="ifRoom" /> <item android:id="@+id/edit_note" android:title="@string/sk_add_note" android:icon="@drawable/ic_fluent_notepad_24_regular" android:showAsAction="ifRoom" />
<item android:id="@+id/biteuser_btn" android:title="@string/sk_bite_user" android:icon="@drawable/ic_fluent_animal_cat_24_regular" android:showAsAction="ifRoom" />
</group> </group>
<group android:id="@+id/menu_group2"> <group android:id="@+id/menu_group2">
<item android:id="@+id/manage_user_lists" android:title="@string/add_user_to_list" android:icon="@drawable/ic_fluent_people_24_regular"/> <item android:id="@+id/manage_user_lists" android:title="@string/add_user_to_list" android:icon="@drawable/ic_fluent_people_24_regular"/>

View file

@ -438,4 +438,7 @@
<string name="sk_poll_multiple_choice">Multiple choices</string> <string name="sk_poll_multiple_choice">Multiple choices</string>
<string name="sk_poll_show_results">Show results</string> <string name="sk_poll_show_results">Show results</string>
<string name="sk_poll_hide_results">Hide results</string> <string name="sk_poll_hide_results">Hide results</string>
<string name="sk_bit_you">%s bit you</string>
<string name="sk_bite_user">Bite</string>
<string name="sk_bite_post">Bite this post</string>
</resources> </resources>