Sending comments with a Giphy gif is available.

This commit is contained in:
Docile-Alligator
2024-09-19 13:23:28 -04:00
parent f042b892cd
commit 4f414eac37
13 changed files with 247 additions and 46 deletions

View File

@ -191,6 +191,8 @@ dependencies {
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'com.giphy.sdk:ui:2.3.15'
/**** Builds and flavors ****/
// debugImplementation because LeakCanary should only run in debug builds.

View File

@ -25,6 +25,10 @@ import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import com.giphy.sdk.core.models.Media;
import com.giphy.sdk.ui.GPHContentType;
import com.giphy.sdk.ui.Giphy;
import com.giphy.sdk.ui.views.GiphyDialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
@ -46,11 +50,9 @@ import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonPlugin;
import io.noties.markwon.core.MarkwonTheme;
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
import ml.docilealligator.infinityforreddit.network.AnyAccountAccessTokenAuthenticator;
import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
import ml.docilealligator.infinityforreddit.thing.UploadedImage;
import ml.docilealligator.infinityforreddit.account.Account;
import ml.docilealligator.infinityforreddit.adapters.MarkdownBottomBarRecyclerViewAdapter;
import ml.docilealligator.infinityforreddit.bottomsheetfragments.AccountChooserBottomSheetFragment;
@ -69,13 +71,17 @@ import ml.docilealligator.infinityforreddit.markdown.EmotePlugin;
import ml.docilealligator.infinityforreddit.markdown.ImageAndGifEntry;
import ml.docilealligator.infinityforreddit.markdown.ImageAndGifPlugin;
import ml.docilealligator.infinityforreddit.markdown.MarkdownUtils;
import ml.docilealligator.infinityforreddit.network.AnyAccountAccessTokenAuthenticator;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
import ml.docilealligator.infinityforreddit.thing.UploadedImage;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
public class CommentActivity extends BaseActivity implements UploadImageEnabledActivity, AccountChooserBottomSheetFragment.AccountChooserListener {
public class CommentActivity extends BaseActivity implements UploadImageEnabledActivity, AccountChooserBottomSheetFragment.AccountChooserListener, GiphyDialogFragment.GifSelectionListener {
public static final String EXTRA_COMMENT_PARENT_TITLE_KEY = "ECPTK";
public static final String EXTRA_COMMENT_PARENT_BODY_KEY = "ECPBK";
@ -124,6 +130,7 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
private boolean isReplying;
private Uri capturedImageUri;
private ArrayList<UploadedImage> uploadedImages = new ArrayList<>();
private GiphyGif giphyGif;
private Menu mMenu;
/**
@ -282,7 +289,7 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
}
MarkdownBottomBarRecyclerViewAdapter adapter = new MarkdownBottomBarRecyclerViewAdapter(
mCustomThemeWrapper, true,
mCustomThemeWrapper, true, true,
new MarkdownBottomBarRecyclerViewAdapter.ItemClickListener() {
@Override
public void onClick(int item) {
@ -300,6 +307,11 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
fragment.setArguments(arguments);
fragment.show(getSupportFragmentManager(), fragment.getTag());
}
@Override
public void onSelectGiphyGif() {
GiphyDialogFragment.Companion.newInstance().show(getSupportFragmentManager(), "giphy_dialog");
}
});
binding.commentMarkdownBottomBarRecyclerView.setLayoutManager(new LinearLayoutManagerBugFixed(this,
@ -313,6 +325,8 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
binding.commentCommentEditText.requestFocus();
Utils.showKeyboard(this, new Handler(), binding.commentCommentEditText);
Giphy.INSTANCE.configure(this, APIUtils.GIPHY_GIF_API_KEY);
}
private void loadCurrentAccount() {
@ -438,7 +452,7 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
.build())
.build();
SendComment.sendComment(this, mExecutor, new Handler(), binding.commentCommentEditText.getText().toString(),
parentFullname, parentDepth, uploadedImages, newAuthenticatorOauthRetrofit, selectedAccount,
parentFullname, parentDepth, uploadedImages, giphyGif, newAuthenticatorOauthRetrofit, selectedAccount,
new SendComment.SendCommentListener() {
@Override
public void sendCommentSuccess(Comment comment) {
@ -599,4 +613,32 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
binding.commentAccountNameTextView.setText(selectedAccount.getAccountName());
}
}
}
@Override
public void didSearchTerm(@NonNull String s) {
}
@Override
public void onGifSelected(@NonNull Media media, @Nullable String s, @NonNull GPHContentType gphContentType) {
this.giphyGif = new GiphyGif(media.getId());
int start = Math.max(binding.commentCommentEditText.getSelectionStart(), 0);
int end = Math.max(binding.commentCommentEditText.getSelectionEnd(), 0);
int realStart = Math.min(start, end);
if (realStart > 0 && binding.commentCommentEditText.getText().toString().charAt(realStart - 1) != '\n') {
binding.commentCommentEditText.getText().replace(realStart, Math.max(start, end),
"\n![gif](" + giphyGif.id + ")\n",
0, "\n![gif]()\n".length() + giphyGif.id.length());
} else {
binding.commentCommentEditText.getText().replace(realStart, Math.max(start, end),
"![gif](" + giphyGif.id + ")\n",
0, "![gif]()\n".length() + giphyGif.id.length());
}
}
@Override
public void onDismissed(@NonNull GPHContentType gphContentType) {
}
}

View File

@ -620,41 +620,6 @@ public class PostPollActivity extends BaseActivity implements FlairBottomSheetFr
mPostingSnackbar.show();
/*Intent intent = new Intent(this, SubmitPostService.class);
intent.putExtra(SubmitPostService.EXTRA_ACCOUNT, selectedAccount);
intent.putExtra(SubmitPostService.EXTRA_SUBREDDIT_NAME, subredditName);
intent.putExtra(SubmitPostService.EXTRA_POST_TYPE, SubmitPostService.EXTRA_POST_TYPE_POLL);
PollPayload payload;
if (!binding.postContentEditTextPostPollActivity.getText().toString().isEmpty()) {
if (uploadedImages.isEmpty()) {
payload = new PollPayload(subredditName, binding.postTitleEditTextPostPollActivity.getText().toString(),
optionList.toArray(new String[0]), (int) binding.votingLengthSliderPostPollActivity.getValue(), isNSFW, isSpoiler, flair,
null, binding.postContentEditTextPostPollActivity.getText().toString(),
binding.receivePostReplyNotificationsSwitchMaterialPostPollActivity.isChecked(),
subredditIsUser ? "profile" : "subreddit");
} else {
try {
payload = new PollPayload(subredditName, binding.postTitleEditTextPostPollActivity.getText().toString(),
optionList.toArray(new String[0]), (int) binding.votingLengthSliderPostPollActivity.getValue(), isNSFW, isSpoiler, flair,
new RichTextJSONConverter().constructRichTextJSON(this, binding.postContentEditTextPostPollActivity.getText().toString(), uploadedImages),
null, binding.receivePostReplyNotificationsSwitchMaterialPostPollActivity.isChecked(),
subredditIsUser ? "profile" : "subreddit");
} catch (JSONException e) {
Snackbar.make(binding.coordinatorLayoutPostPollActivity, R.string.convert_to_richtext_json_failed, Snackbar.LENGTH_SHORT).show();
return;
}
}
} else {
payload = new PollPayload(subredditName, binding.postTitleEditTextPostPollActivity.getText().toString(),
optionList.toArray(new String[0]), (int) binding.votingLengthSliderPostPollActivity.getValue(), isNSFW, isSpoiler, flair,
binding.receivePostReplyNotificationsSwitchMaterialPostPollActivity.isChecked(),
subredditIsUser ? "profile" : "subreddit");
}
intent.putExtra(SubmitPostService.EXTRA_POLL_PAYLOAD, new Gson().toJson(payload));
ContextCompat.startForegroundService(this, intent);*/
PersistableBundle extras = new PersistableBundle();
extras.putString(SubmitPostService.EXTRA_ACCOUNT, selectedAccount.getJSONModel());
extras.putString(SubmitPostService.EXTRA_SUBREDDIT_NAME, subredditName);

View File

@ -31,28 +31,38 @@ public class MarkdownBottomBarRecyclerViewAdapter extends RecyclerView.Adapter<R
public static final int QUOTE = 9;
public static final int CODE_BLOCK = 10;
public static final int UPLOAD_IMAGE = 11;
public static final int GIPHY_GIF = 12;
private static final int ITEM_COUNT = 11;
private final CustomThemeWrapper customThemeWrapper;
private final boolean canUploadImage;
private final boolean canSendGiphyGIf;
private final ItemClickListener itemClickListener;
public interface ItemClickListener {
void onClick(int item);
void onUploadImage();
default void onSelectGiphyGif() {}
}
public MarkdownBottomBarRecyclerViewAdapter(CustomThemeWrapper customThemeWrapper,
ItemClickListener itemClickListener) {
this(customThemeWrapper, false, itemClickListener);
this(customThemeWrapper, false, false, itemClickListener);
}
public MarkdownBottomBarRecyclerViewAdapter(CustomThemeWrapper customThemeWrapper,
boolean canUploadImage,
ItemClickListener itemClickListener) {
this(customThemeWrapper, canUploadImage, false, itemClickListener);
}
public MarkdownBottomBarRecyclerViewAdapter(CustomThemeWrapper customThemeWrapper,
boolean canUploadImage, boolean canSendGiphyGif,
ItemClickListener itemClickListener) {
this.customThemeWrapper = customThemeWrapper;
this.canUploadImage = canUploadImage;
this.canSendGiphyGIf = canSendGiphyGif;
this.itemClickListener = itemClickListener;
}
@ -102,13 +112,16 @@ public class MarkdownBottomBarRecyclerViewAdapter extends RecyclerView.Adapter<R
case UPLOAD_IMAGE:
((MarkdownBottomBarItemViewHolder) holder).imageView.setImageResource(R.drawable.ic_image_day_night_24dp);
break;
case GIPHY_GIF:
((MarkdownBottomBarItemViewHolder) holder).imageView.setImageResource(R.drawable.ic_gif_24dp);
break;
}
}
}
@Override
public int getItemCount() {
return canUploadImage ? ITEM_COUNT + 1 : ITEM_COUNT;
return canUploadImage ? (canSendGiphyGIf ? ITEM_COUNT + 2 : ITEM_COUNT + 1) : ITEM_COUNT;
}
public static void bindEditTextWithItemClickListener(Activity activity, EditText commentEditText, int item) {
@ -324,6 +337,8 @@ public class MarkdownBottomBarRecyclerViewAdapter extends RecyclerView.Adapter<R
int position = getBindingAdapterPosition();
if (position == UPLOAD_IMAGE) {
itemClickListener.onUploadImage();
} else if (position == GIPHY_GIF) {
itemClickListener.onSelectGiphyGif();
} else {
itemClickListener.onClick(position);
}

View File

@ -14,6 +14,7 @@ import java.util.Map;
import java.util.concurrent.Executor;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
import ml.docilealligator.infinityforreddit.thing.UploadedImage;
import ml.docilealligator.infinityforreddit.account.Account;
import ml.docilealligator.infinityforreddit.apis.RedditAPI;
@ -27,16 +28,16 @@ import retrofit2.Retrofit;
public class SendComment {
public static void sendComment(Context context, Executor executor, Handler handler,
String commentMarkdown, String thingFullname, int parentDepth,
List<UploadedImage> uploadedImages,
List<UploadedImage> uploadedImages, @Nullable GiphyGif giphyGif,
Retrofit newAuthenticatorOauthRetrofit, Account account,
SendCommentListener sendCommentListener) {
Map<String, String> headers = APIUtils.getOAuthHeader(account.getAccessToken());
Map<String, String> params = new HashMap<>();
params.put(APIUtils.API_TYPE_KEY, "json");
params.put(APIUtils.RETURN_RTJSON_KEY, "true");
if (!uploadedImages.isEmpty()) {
if (!uploadedImages.isEmpty() || giphyGif != null) {
try {
params.put(APIUtils.RICHTEXT_JSON_KEY, new RichTextJSONConverter().constructRichTextJSON(context, commentMarkdown, uploadedImages));
params.put(APIUtils.RICHTEXT_JSON_KEY, new RichTextJSONConverter().constructRichTextJSON(context, commentMarkdown, uploadedImages, giphyGif));
params.put(APIUtils.TEXT_KEY, "");
} catch (JSONException e) {
sendCommentListener.sendCommentFailed(context.getString(R.string.convert_to_richtext_json_failed));

View File

@ -0,0 +1,13 @@
package ml.docilealligator.infinityforreddit.markdown;
import org.commonmark.node.CustomBlock;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
public class GiphyGifBlock extends CustomBlock {
public GiphyGif giphyGif;
public GiphyGifBlock(GiphyGif giphyGif) {
this.giphyGif = giphyGif;
}
}

View File

@ -0,0 +1,65 @@
package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.Nullable;
import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory;
import org.commonmark.parser.block.BlockContinue;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
public class GiphyGifBlockParser extends AbstractBlockParser {
private final GiphyGifBlock giphyGifBlock;
GiphyGifBlockParser(GiphyGif giphyGif) {
this.giphyGifBlock = new GiphyGifBlock(giphyGif);
}
@Override
public Block getBlock() {
return giphyGifBlock;
}
@Override
public BlockContinue tryContinue(ParserState parserState) {
return null;
}
public static class Factory extends AbstractBlockParserFactory {
private final Pattern pattern = Pattern.compile("!\\[gif]\\(giphy\\|\\w+\\|downsized\\)");
@Nullable
private GiphyGif giphyGif;
public Factory(@Nullable GiphyGif giphyGif) {
this.giphyGif = giphyGif;
}
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
if (giphyGif == null) {
return BlockStart.none();
}
String line = state.getLine().toString();
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
int startIndex = line.lastIndexOf('(');
if (startIndex > 0) {
int endIndex = line.indexOf(')', startIndex);
String id = line.substring(startIndex + 1, endIndex);
if (giphyGif.id.equals(id)) {
return BlockStart.of(new GiphyGifBlockParser(giphyGif));
}
}
}
return BlockStart.none();
}
}
}

View File

@ -0,0 +1,28 @@
package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.parser.Parser;
import io.noties.markwon.AbstractMarkwonPlugin;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
public class GiphyGifPlugin extends AbstractMarkwonPlugin {
private final GiphyGifBlockParser.Factory factory;
public GiphyGifPlugin(@Nullable GiphyGif giphyGif) {
this.factory = new GiphyGifBlockParser.Factory(giphyGif);
}
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(markdown);
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.customBlockParserFactory(factory);
}
}

View File

@ -76,6 +76,26 @@ public class MarkdownUtils {
.build();
}
@NonNull
public static Markwon createContentSubmissionRedditMarkwon(@NonNull Context context,
@NonNull UploadedImagePlugin uploadedImagePlugin,
@NonNull GiphyGifPlugin giphyGifPlugin) {
return Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
}))
.usePlugin(SuperscriptPlugin.create())
.usePlugin(SpoilerParserPlugin.create(0, 0))
.usePlugin(RedditHeadingPlugin.create())
.usePlugin(StrikethroughPlugin.create())
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(giphyGifPlugin)
.usePlugin(uploadedImagePlugin)
.usePlugin(TableEntryPlugin.create(context))
.build();
}
@NonNull
public static Markwon createContentPreviewRedditMarkwon(@NonNull Context context,
@NonNull MarkwonPlugin miscPlugin,

View File

@ -47,6 +47,7 @@ import java.util.Stack;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonReducer;
import ml.docilealligator.infinityforreddit.thing.GiphyGif;
import ml.docilealligator.infinityforreddit.thing.UploadedImage;
public class RichTextJSONConverter implements Visitor {
@ -69,6 +70,7 @@ public class RichTextJSONConverter implements Visitor {
private static final String SPOILER_E = "spoilertext";
private static final String TABLE_E = "table";
private static final String IMAGE_E = "img";
private static final String GIF_E = "gif";
private static final String TYPE = "e";
private static final String CONTENT = "c";
@ -126,6 +128,25 @@ public class RichTextJSONConverter implements Visitor {
return richText.toString();
}
public String constructRichTextJSON(Context context, String markdown,
List<UploadedImage> uploadedImages, @Nullable GiphyGif giphyGif) throws JSONException {
UploadedImagePlugin uploadedImagePlugin = new UploadedImagePlugin();
uploadedImagePlugin.setUploadedImages(uploadedImages);
Markwon markwon = MarkdownUtils.createContentSubmissionRedditMarkwon(
context, uploadedImagePlugin, new GiphyGifPlugin(giphyGif));
List<Node> nodes = MarkwonReducer.directChildren().reduce(markwon.parse(markdown));
JSONObject richText = new JSONObject();
for (Node n : nodes) {
n.accept(this);
}
richText.put(DOCUMENT, document);
return richText.toString();
}
public JSONObject constructRichTextJSON(List<Node> nodes) throws JSONException {
JSONObject richText = new JSONObject();
@ -579,6 +600,17 @@ public class RichTextJSONConverter implements Visitor {
nodeJSON.put(IMAGE_ID, ((UploadedImageBlock) customBlock).uploadeImage.imageUrlOrKey);
nodeJSON.put(CONTENT, ((UploadedImageBlock) customBlock).uploadeImage.getCaption());
document.put(nodeJSON);
} catch (JSONException e) {
throw new RuntimeException(e);
}
} else if (customBlock instanceof GiphyGifBlock) {
//Nothing is allowed inside this block.
try {
JSONObject nodeJSON = new JSONObject();
nodeJSON.put(TYPE, GIF_E);
nodeJSON.put(IMAGE_ID, ((GiphyGifBlock) customBlock).giphyGif.id);
document.put(nodeJSON);
} catch (JSONException e) {
throw new RuntimeException(e);

View File

@ -0,0 +1,8 @@
package ml.docilealligator.infinityforreddit.thing;
public class GiphyGif {
public final String id;
public GiphyGif(String id) {
this.id = "giphy|" + id + "|downsized";
}
}

View File

@ -32,6 +32,7 @@ public class APIUtils {
public static final String IMGUR_CLIENT_ID = "Client-ID cc671794e0ab397";
public static final String REDGIFS_CLIENT_ID = "1828d0bcc93-15ac-bde6-0005-d2ecbe8daab3";
public static final String REDGIFS_CLIENT_SECRET = "TJBlw7jRXW65NAGgFBtgZHu97WlzRXHYybK81sZ9dLM=";
public static final String GIPHY_GIF_API_KEY = "";
public static final String RESPONSE_TYPE_KEY = "response_type";
public static final String RESPONSE_TYPE = "code";
public static final String STATE_KEY = "state";

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,680q-33,0 -56.5,-23.5T80,600v-240q0,-33 23.5,-56.5T160,280h200q17,0 28.5,11.5T400,320q0,17 -11.5,28.5T360,360L160,360v240h160v-80h-40q-17,0 -28.5,-11.5T240,480q0,-17 11.5,-28.5T280,440h80q17,0 28.5,11.5T400,480v120q0,33 -23.5,56.5T320,680L160,680ZM480,640v-320q0,-17 11.5,-28.5T520,280q17,0 28.5,11.5T560,320v320q0,17 -11.5,28.5T520,680q-17,0 -28.5,-11.5T480,640ZM640,640v-320q0,-17 11.5,-28.5T680,280h200q17,0 28.5,11.5T920,320q0,17 -11.5,28.5T880,360L720,360v80h120q17,0 28.5,11.5T880,480q0,17 -11.5,28.5T840,520L720,520v120q0,17 -11.5,28.5T680,680q-17,0 -28.5,-11.5T640,640Z"
android:fillColor="#e8eaed"/>
</vector>