Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
import ml.docilealligator.infinityforreddit.settings.SecurityPreferenceFragment;
import ml.docilealligator.infinityforreddit.settings.ThemePreferenceFragment;
import ml.docilealligator.infinityforreddit.settings.TranslationFragment;
import ml.docilealligator.infinityforreddit.settings.TranslationPreferenceFragment;
import ml.docilealligator.infinityforreddit.settings.VideoPreferenceFragment;
import ml.docilealligator.infinityforreddit.worker.MaterialYouWorker;
import ml.docilealligator.infinityforreddit.worker.PullNotificationWorker;
Expand Down Expand Up @@ -251,6 +252,8 @@ public interface AppComponent {

void inject(TranslationFragment translationFragment);

void inject(TranslationPreferenceFragment translationPreferenceFragment);

void inject(FetchRandomSubredditOrPostActivity fetchRandomSubredditOrPostActivity);

void inject(MiscellaneousPreferenceFragment miscellaneousPreferenceFragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import com.google.gson.Gson;

import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LoopAvailableExoCreator;
import ml.docilealligator.infinityforreddit.translation.TranslatorProvider;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.CustomThemeSharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
Expand Down Expand Up @@ -219,4 +222,10 @@ static ExoCreator provideExoCreator(Config config,
static Executor provideExecutor() {
return Executors.newFixedThreadPool(4);
}

@Provides
@Singleton
static Gson provideGson() {
return new Gson();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,21 @@ public void failed() {
}
}

public void translateComment(@NonNull Comment comment, int position) {
// TODO: Implement comment translation
Toast.makeText(this, R.string.translating, Toast.LENGTH_SHORT).show();

ViewPostDetailFragment fragment = mSectionsPagerAdapter.getCurrentFragment();
if (fragment != null) {
fragment.translateComment(comment, position);
}
}

public void translatePost(@NonNull Post post, int position) {
// TODO: Implement post translation
Toast.makeText(this, R.string.translating, Toast.LENGTH_SHORT).show();
}

public boolean toggleSearchPanelVisibility() {
if (binding.searchPanelMaterialCardViewViewPostDetailActivity.getVisibility() == View.GONE) {
binding.searchPanelMaterialCardViewViewPostDetailActivity.setVisibility(View.VISIBLE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
});
}

binding.translateTextViewCommentMoreBottomSheetFragment.setOnClickListener(view -> {
if (activity instanceof ViewPostDetailActivity) {
((ViewPostDetailActivity) activity).translateComment(comment, bundle.getInt(EXTRA_POSITION));
}
dismiss();
});

binding.shareTextViewCommentMoreBottomSheetFragment.setOnClickListener(view -> {
dismiss();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ public void failed() {
dismiss();
});

binding.translateTextViewPostOptionsBottomSheetFragment.setOnClickListener(view -> {
((PostOptionsBottomSheetFragmentListener) mBaseActivity).translatePost(mPost, getArguments().getInt(EXTRA_POST_LIST_POSITION, 0));
dismiss();
});

binding.reportTextViewPostOptionsBottomSheetFragment.setOnClickListener(view -> {
Intent intent = new Intent(mBaseActivity, ReportActivity.class);
intent.putExtra(ReportActivity.EXTRA_SUBREDDIT_NAME, mPost.getSubredditName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public Comment[] newArray(int size) {
private boolean loadMoreChildrenFailed;
private long editedTimeMillis;
private Map<String, MediaMetadata> mediaMetadataMap;

// Translation fields
private String translatedCommentRawText;
private boolean isTranslated;

public Comment(String id, String fullName, String author, String authorFullName, String authorFlair,
String authorFlairHTML, String linkAuthor,
Expand Down Expand Up @@ -182,6 +186,8 @@ protected Comment(Parcel in) {
isLoadingMoreChildren = in.readByte() != 0;
loadMoreChildrenFailed = in.readByte() != 0;
mediaMetadataMap = (Map<String, MediaMetadata>) in.readValue(getClass().getClassLoader());
translatedCommentRawText = in.readString();
isTranslated = in.readByte() != 0;
}

public String getId() {
Expand Down Expand Up @@ -510,6 +516,22 @@ public void setMediaMetadataMap(Map<String, MediaMetadata> mediaMetadataMap) {
this.mediaMetadataMap = mediaMetadataMap;
}

public String getTranslatedCommentRawText() {
return translatedCommentRawText;
}

public void setTranslatedCommentRawText(String translatedCommentRawText) {
this.translatedCommentRawText = translatedCommentRawText;
}

public boolean isTranslated() {
return isTranslated;
}

public void setTranslated(boolean translated) {
isTranslated = translated;
}

@Override
public int describeContents() {
return 0;
Expand Down Expand Up @@ -560,5 +582,7 @@ public void writeToParcel(Parcel parcel, int i) {
parcel.writeByte((byte) (isLoadingMoreChildren ? 1 : 0));
parcel.writeByte((byte) (loadMoreChildrenFailed ? 1 : 0));
parcel.writeValue(mediaMetadataMap);
parcel.writeString(translatedCommentRawText);
parcel.writeByte((byte) (isTranslated ? 1 : 0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,12 @@ public void saveComment(int position, boolean isSaved) {
}
}

public void translateComment(Comment comment, int position) {
if (mCommentsAdapter != null) {
mCommentsAdapter.translateComment(position, comment);
}
}

public void searchComment(String query, boolean searchNextComment) {
if (mCommentsAdapter != null) {
ArrayList<Comment> visibleComments = mCommentsAdapter.getVisibleComments();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public class Post implements Parcelable {
private String approvedBy;
private boolean removed;
private boolean spam;

// Translation fields
private String translatedTitle;
private String translatedSelfText;
private boolean isTranslated;

//Text and video posts
public Post(String id, String fullName, String subredditName, String subredditNamePrefixed,
Expand Down Expand Up @@ -226,6 +231,9 @@ protected Post(Parcel in) {
previews = in.createTypedArrayList(Preview.CREATOR);
mediaMetadataMap = (Map<String, MediaMetadata>) in.readValue(getClass().getClassLoader());
gallery = in.createTypedArrayList(Gallery.CREATOR);
translatedTitle = in.readString();
translatedSelfText = in.readString();
isTranslated = in.readByte() != 0;
}

public static final Creator<Post> CREATOR = new Creator<Post>() {
Expand Down Expand Up @@ -571,6 +579,9 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedList(previews);
dest.writeValue(mediaMetadataMap);
dest.writeTypedList(gallery);
dest.writeString(translatedTitle);
dest.writeString(translatedSelfText);
dest.writeByte((byte) (isTranslated ? 1 : 0));
}

public boolean isStickied() {
Expand Down Expand Up @@ -695,6 +706,30 @@ public void setMp4Variant(String mp4Variant) {
this.mp4Variant = mp4Variant;
}

public String getTranslatedTitle() {
return translatedTitle;
}

public void setTranslatedTitle(String translatedTitle) {
this.translatedTitle = translatedTitle;
}

public String getTranslatedSelfText() {
return translatedSelfText;
}

public void setTranslatedSelfText(String translatedSelfText) {
this.translatedSelfText = translatedSelfText;
}

public boolean isTranslated() {
return isTranslated;
}

public void setTranslated(boolean translated) {
isTranslated = translated;
}

@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Post)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ml.docilealligator.infinityforreddit.settings;

import android.os.Bundle;
import android.text.InputType;

import androidx.preference.EditTextPreference;

import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.customviews.preference.CustomFontPreferenceFragmentCompat;

public class TranslationPreferenceFragment extends CustomFontPreferenceFragmentCompat {

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.translation_preferences, rootKey);

// Set API key fields to password mode
EditTextPreference googleApiKeyPref = findPreference("google_translation_api_key");
if (googleApiKeyPref != null) {
googleApiKeyPref.setOnBindEditTextListener(editText -> {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
editText.setSelection(editText.getText().length());
});
googleApiKeyPref.setSummaryProvider(preference -> {
String value = ((EditTextPreference) preference).getText();
if (value == null || value.isEmpty()) {
return getString(R.string.settings_google_translation_api_key_summary);
}
return "••••••••";
});
}

EditTextPreference deeplApiKeyPref = findPreference("deepl_api_key");
if (deeplApiKeyPref != null) {
deeplApiKeyPref.setOnBindEditTextListener(editText -> {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
editText.setSelection(editText.getText().length());
});
deeplApiKeyPref.setSummaryProvider(preference -> {
String value = ((EditTextPreference) preference).getText();
if (value == null || value.isEmpty()) {
return getString(R.string.settings_deepl_api_key_summary);
}
return "••••••••";
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package ml.docilealligator.infinityforreddit.translation;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class DeepLTranslator implements Translator {
private static final String API_URL = "https://api-free.deepl.com/v2/translate";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

private final OkHttpClient httpClient;
private final Executor executor;
private final Gson gson;
private String apiKey;

public DeepLTranslator(@NonNull OkHttpClient httpClient,
@NonNull Executor executor,
@NonNull Gson gson) {
this.httpClient = httpClient;
this.executor = executor;
this.gson = gson;
}

public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}

@Override
public CompletableFuture<TranslationResult> translate(@NonNull String text,
@NonNull String sourceLanguage,
@NonNull String targetLanguage) {
CompletableFuture<TranslationResult> future = new CompletableFuture<>();

if (!isConfigured()) {
future.complete(TranslationResult.error("DeepL API key not configured"));
return future;
}

executor.execute(() -> {
try {
JsonObject json = new JsonObject();
JsonArray textArray = new JsonArray();
textArray.add(text);
json.add("text", textArray);
json.addProperty("target_lang", targetLanguage.toUpperCase());
if (!"auto".equalsIgnoreCase(sourceLanguage)) {
json.addProperty("source_lang", sourceLanguage.toUpperCase());
}

String jsonString = gson.toJson(json);
RequestBody body = RequestBody.create(jsonString, JSON);

Request request = new Request.Builder()
.url(API_URL)
.addHeader("Authorization", "DeepL-Auth-Key " + apiKey)
.post(body)
.build();

httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
future.complete(TranslationResult.error("Network error: " + e.getMessage()));
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
try {
if (!response.isSuccessful()) {
future.complete(TranslationResult.error("API error: " + response.code()));
return;
}

if (response.body() == null) {
future.complete(TranslationResult.error("Empty response body"));
return;
}

String responseBody = response.body().string();
JsonObject responseJson = gson.fromJson(responseBody, JsonObject.class);

if (responseJson.has("translations")) {
JsonArray translations = responseJson.getAsJsonArray("translations");
if (translations.size() > 0) {
JsonObject translation = translations.get(0).getAsJsonObject();
String translatedText = translation.get("text").getAsString();
String detectedLanguage = translation.has("detected_source_language")
? translation.get("detected_source_language").getAsString()
: null;
future.complete(TranslationResult.success(translatedText, detectedLanguage));
} else {
future.complete(TranslationResult.error("No translation returned"));
}
} else {
future.complete(TranslationResult.error("Invalid response format"));
}
} catch (Exception e) {
future.complete(TranslationResult.error("Parse error: " + e.getMessage()));
} finally {
response.close();
}
}
});
} catch (Exception e) {
future.complete(TranslationResult.error("Request error: " + e.getMessage()));
}
});

return future;
}

@Override
public boolean isConfigured() {
return apiKey != null && !apiKey.isEmpty();
}

@Override
public String getName() {
return "DeepL";
}
}
Loading
Loading