From 8a5b36db9629a03a533f89cc9467c89474769da7 Mon Sep 17 00:00:00 2001 From: FineFindus <63370021+FineFindus@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:37:31 +0100 Subject: [PATCH] feat(profile): add note (#918) * feat(profile): add note * simplify note code * adjust spacing * size and hitbox adjustments, progress * profile menu item to add note --------- Co-authored-by: sk --- .../api/requests/accounts/SetPrivateNote.java | 19 +++ .../android/fragments/ProfileFragment.java | 126 +++++++++++++++++- .../src/main/res/drawable/bg_note_edit.xml | 7 + .../ic_fluent_person_note_24_regular.xml | 3 + .../src/main/res/layout/fragment_profile.xml | 64 ++++++++- mastodon/src/main/res/menu/profile.xml | 5 +- mastodon/src/main/res/values/strings_sk.xml | 6 + 7 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java create mode 100644 mastodon/src/main/res/drawable/bg_note_edit.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java new file mode 100644 index 0000000000..a1b59a4c7e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java @@ -0,0 +1,19 @@ +package org.joinmastodon.android.api.requests.accounts; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Relationship; + +public class SetPrivateNote extends MastodonAPIRequest{ + public SetPrivateNote(String id, String comment){ + super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class); + Request req = new Request(comment); + setRequestBody(req); + } + + private static class Request{ + public String comment; + public Request(String comment){ + this.comment=comment; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index fd30387ba1..274630dae6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -21,8 +21,11 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.text.Editable; +import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.text.TextWatcher; import android.transition.ChangeBounds; import android.transition.Fade; import android.transition.TransitionManager; @@ -56,6 +59,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; +import org.joinmastodon.android.api.requests.accounts.SetPrivateNote; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; import org.joinmastodon.android.api.requests.instance.GetInstance; import org.joinmastodon.android.api.session.AccountSessionManager; @@ -145,7 +149,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private SwipeRefreshLayout refreshLayout; private View followersBtn, followingBtn; private EditText nameEdit, bioEdit; - private ProgressBar actionProgress, notifyProgress; + private ProgressBar actionProgress, notifyProgress, noteSaveProgress; private FrameLayout[] tabViews; private TabLayoutMediator tabLayoutMediator; private TextView followsYouView; @@ -186,6 +190,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback()); private ListImageLoaderWrapper imgLoader; + // profile note + private FrameLayout noteWrap; + private ImageButton noteSaveBtn; + private EditText noteEdit; + @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -257,6 +266,7 @@ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bu bioEditWrap=content.findViewById(R.id.bio_edit_wrap); actionProgress=content.findViewById(R.id.action_progress); notifyProgress=content.findViewById(R.id.notify_progress); + noteSaveProgress=content.findViewById(R.id.note_save_progress); fab=content.findViewById(R.id.fab); followsYouView=content.findViewById(R.id.follows_you); countersLayout=content.findViewById(R.id.profile_counters); @@ -271,6 +281,51 @@ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bu avatar.setOutlineProvider(OutlineProviders.roundedRect(24)); avatar.setClipToOutline(true); + noteEdit=content.findViewById(R.id.note_edit); + noteWrap=content.findViewById(R.id.note_edit_wrap); + noteSaveBtn=content.findViewById(R.id.note_save_btn); + + noteSaveBtn.setOnClickListener((v->{ + savePrivateNote(noteEdit.getText().toString()); + InputMethodManager imm=(InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0); + noteEdit.clearFocus(); + noteSaveBtn.clearFocus(); + })); + + + noteEdit.setOnFocusChangeListener((v, hasFocus)->{ + if(hasFocus){ + hideFab(); + V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE); + noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + }else if(!noteSaveBtn.hasFocus()){ + showFab(); + hideNoteSaveBtnIfNotDirty(); + } + }); + + noteEdit.addTextChangedListener(new TextWatcher(){ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after){} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count){ + if(relationship!=null && noteSaveBtn.getVisibility()!=View.VISIBLE && !s.toString().equals(relationship.note)) + V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE); + } + + @Override + public void afterTextChanged(Editable s){} + }); + + noteSaveBtn.setOnFocusChangeListener((v, hasFocus)->{ + if(!hasFocus && !noteEdit.hasFocus()){ + showFab(); + hideNoteSaveBtnIfNotDirty(); + } + }); + FrameLayout sizeWrapper=new FrameLayout(getActivity()){ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ @@ -435,6 +490,46 @@ public void onError(ErrorResponse error){ return sizeWrapper; } + private void hideNoteSaveBtnIfNotDirty(){ + if(noteEdit.getText().toString().equals(relationship.note)){ + V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE); + } + } + + private void showPrivateNote(){ + noteWrap.setVisibility(View.VISIBLE); + noteEdit.setText(relationship.note); + } + + private void hidePrivateNote(){ + noteWrap.setVisibility(View.GONE); + noteEdit.setText(null); + } + + private void savePrivateNote(String note){ + if(note!=null && note.equals(relationship.note)){ + updateRelationship(); + invalidateOptionsMenu(); + return; + } + V.setVisibilityAnimated(noteSaveProgress, View.VISIBLE); + V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE); + new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() { + @Override + public void onSuccess(Relationship result) { + updateRelationship(result); + invalidateOptionsMenu(); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + V.setVisibilityAnimated(noteSaveProgress, View.GONE); + V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE); + } + }).exec(accountID); + } + private void onAccountLoaded(Account result) { account=result; isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); @@ -793,6 +888,8 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ }else{ blockDomain.setVisible(false); } + menu.findItem(R.id.edit_note).setTitle(noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty()) + ? R.string.sk_add_note : R.string.sk_delete_note); } @Override @@ -874,6 +971,26 @@ public void onError(ErrorResponse error){ }else if(id==R.id.save){ if(isInEditMode) saveAndExitEditMode(); + }else if(id==R.id.edit_note){ + if(noteWrap.getVisibility()==View.GONE){ + showPrivateNote(); + UiUtils.beginLayoutTransition(scrollableContent); + noteEdit.requestFocus(); + noteEdit.postDelayed(()->{ + InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class); + imm.showSoftInput(noteEdit, 0); + }, 100); + }else if(relationship.note.isEmpty()){ + hidePrivateNote(); + UiUtils.beginLayoutTransition(scrollableContent); + }else{ + new M3AlertDialogBuilder(getActivity()) + .setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername())) + .setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null)) + .setNegativeButton(R.string.cancel, null) + .show(); + } + invalidateOptionsMenu(); } return true; } @@ -899,15 +1016,22 @@ public void onError(ErrorResponse error){ private void updateRelationship(){ if(getActivity()==null) return; + if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote(); + else hidePrivateNote(); invalidateOptionsMenu(); actionButton.setVisibility(View.VISIBLE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); UiUtils.setRelationshipToActionButtonM3(relationship, actionButton); actionProgress.setIndeterminateTintList(actionButton.getTextColors()); notifyProgress.setIndeterminateTintList(notifyButton.getTextColors()); + noteSaveProgress.setIndeterminateTintList(noteEdit.getTextColors()); followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE); notifyButton.setSelected(relationship.notifying); notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username)); + noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + V.setVisibilityAnimated(noteSaveProgress, View.GONE); + V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE); + UiUtils.beginLayoutTransition(scrollableContent); } public ImageButton getFab() { diff --git a/mastodon/src/main/res/drawable/bg_note_edit.xml b/mastodon/src/main/res/drawable/bg_note_edit.xml new file mode 100644 index 0000000000..721d759239 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_note_edit.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml new file mode 100644 index 0000000000..ad42876903 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/fragment_profile.xml b/mastodon/src/main/res/layout/fragment_profile.xml index e79bdc0f2a..66a938fa18 100644 --- a/mastodon/src/main/res/layout/fragment_profile.xml +++ b/mastodon/src/main/res/layout/fragment_profile.xml @@ -50,7 +50,7 @@ android:text="@string/follows_you" android:textAllCaps="true" android:textColor="#fff" - android:textSize="14dp" + android:textSize="14sp" android:visibility="gone" tools:visibility="visible" /> @@ -80,11 +80,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/cover" - android:layout_alignParentEnd="true"> + android:layout_alignParentEnd="true" + android:clipChildren="false"> @@ -95,7 +97,8 @@ style="@style/Widget.Mastodon.M3.Button.Tonal" android:background="@drawable/bg_button_m3_tonal_circle_selector" android:paddingStart="12dp" - android:drawableStart="@drawable/ic_fluent_alert_24_selector" /> + android:drawableStart="@drawable/ic_fluent_alert_24_selector" + tools:ignore="RtlSymmetry" /> @@ -213,6 +217,60 @@ + + + + + + + + + + + + + + + + + @@ -9,7 +12,7 @@ - + diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 5717077c7a..af85a5edd0 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -417,4 +417,10 @@ Manually approve new followers Default posting visibility Mutuals + Add a personal note about this profile + Confirm changes to note + Failed to save note + Delete personal note about %s? + Delete personal note + Add personal note \ No newline at end of file