diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 60393587..826dc004 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -394,6 +394,11 @@ android:parentActivityName=".activities.MainActivity" android:theme="@style/AppTheme.SlidableWithTranslucentWindow" /> + + + \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 2fc08bd9..c33a6deb 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -14,6 +14,7 @@ import ml.docilealligator.infinityforreddit.activities.CustomizePostFilterActivi import ml.docilealligator.infinityforreddit.activities.CustomizeThemeActivity; import ml.docilealligator.infinityforreddit.activities.EditCommentActivity; import ml.docilealligator.infinityforreddit.activities.EditMultiRedditActivity; +import ml.docilealligator.infinityforreddit.activities.EditProfileActivity; import ml.docilealligator.infinityforreddit.activities.EditPostActivity; import ml.docilealligator.infinityforreddit.activities.FetchRandomSubredditOrPostActivity; import ml.docilealligator.infinityforreddit.activities.FilteredPostsActivity; @@ -78,6 +79,7 @@ import ml.docilealligator.infinityforreddit.fragments.ViewRedditGalleryImageOrGi import ml.docilealligator.infinityforreddit.fragments.ViewRedditGalleryVideoFragment; import ml.docilealligator.infinityforreddit.services.DownloadMediaService; import ml.docilealligator.infinityforreddit.services.DownloadRedditVideoService; +import ml.docilealligator.infinityforreddit.services.EditProfileService; import ml.docilealligator.infinityforreddit.services.MaterialYouService; import ml.docilealligator.infinityforreddit.services.SubmitPostService; import ml.docilealligator.infinityforreddit.settings.AdvancedPreferenceFragment; @@ -286,4 +288,8 @@ public interface AppComponent { void inject(WikiActivity wikiActivity); void inject(Infinity infinity); + + void inject(EditProfileService editProfileService); + + void inject(EditProfileActivity editProfileActivity); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java index eda22c35..229e047c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java @@ -37,7 +37,7 @@ import ml.docilealligator.infinityforreddit.user.UserData; @Database(entities = {Account.class, SubredditData.class, SubscribedSubredditData.class, UserData.class, SubscribedUserData.class, MultiReddit.class, CustomTheme.class, RecentSearchQuery.class, - ReadPost.class, PostFilter.class, PostFilterUsage.class, AnonymousMultiredditSubreddit.class}, version = 21) + ReadPost.class, PostFilter.class, PostFilterUsage.class, AnonymousMultiredditSubreddit.class}, version = 22) public abstract class RedditDataRoomDatabase extends RoomDatabase { private static RedditDataRoomDatabase INSTANCE; @@ -51,7 +51,8 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13, MIGRATION_13_14, MIGRATION_14_15, MIGRATION_15_16, MIGRATION_16_17, - MIGRATION_17_18, MIGRATION_18_19, MIGRATION_19_20, MIGRATION_20_21) + MIGRATION_17_18, MIGRATION_18_19, MIGRATION_19_20, MIGRATION_20_21, + MIGRATION_21_22) .build(); } } @@ -358,4 +359,10 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { database.execSQL("ALTER TABLE post_filter ADD COLUMN contain_domains TEXT"); } }; + private static final Migration MIGRATION_21_22 = new Migration(21, 22) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE users ADD COLUMN title TEXT"); + } + }; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditProfileActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditProfileActivity.java new file mode 100644 index 00000000..fadef9b8 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditProfileActivity.java @@ -0,0 +1,351 @@ +package ml.docilealligator.infinityforreddit.activities; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import butterknife.BindView; +import butterknife.ButterKnife; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.r0adkll.slidr.Slidr; +import com.r0adkll.slidr.model.SlidrInterface; +import jp.wasabeef.glide.transformations.RoundedCornersTransformation; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.events.SubmitChangeAvatarEvent; +import ml.docilealligator.infinityforreddit.events.SubmitChangeBannerEvent; +import ml.docilealligator.infinityforreddit.events.SubmitSaveProfileEvent; +import ml.docilealligator.infinityforreddit.services.EditProfileService; +import ml.docilealligator.infinityforreddit.user.UserViewModel; +import ml.docilealligator.infinityforreddit.utils.EditProfileUtils; +import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import retrofit2.Retrofit; + +import javax.inject.Inject; +import javax.inject.Named; + +public class EditProfileActivity extends BaseActivity { + + private static final int PICK_IMAGE_BANNER_REQUEST_CODE = 0x401; + private static final int PICK_IMAGE_AVATAR_REQUEST_CODE = 0x402; + + @BindView(R.id.root_layout_view_edit_profile_activity) + LinearLayout root; + @BindView(R.id.content_view_edit_profile_activity) + LinearLayout content; + @BindView(R.id.toolbar_view_edit_profile_activity) + Toolbar toolbar; + @BindView(R.id.appbar_layout_view_edit_profile_activity) + AppBarLayout appBarLayout; + @BindView(R.id.image_view_banner_edit_profile_activity) + ImageView bannerImageView; + @BindView(R.id.image_view_avatar_edit_profile_activity) + ImageView avatarImageView; + @BindView(R.id.image_view_change_banner_edit_profile_activity) + ImageView changeBanner; + @BindView(R.id.image_view_change_avatar_edit_profile_activity) + ImageView changeAvatar; + @BindView(R.id.edit_text_display_name_edit_profile_activity) + EditText editTextDisplayName; + @BindView(R.id.edit_text_about_you_edit_profile_activity) + EditText editTextAboutYou; + + @Inject + @Named("current_account") + SharedPreferences mCurrentAccountSharedPreferences; + @Inject + @Named("default") + SharedPreferences mSharedPreferences; + @Inject + @Named("oauth") + Retrofit mOauthRetrofit; + @Inject + RedditDataRoomDatabase mRedditDataRoomDatabase; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + + // + private String mAccountName; + private String mAccessToken; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + ((Infinity) getApplication()).getAppComponent().inject(this); + setTransparentStatusBarAfterToolbarCollapsed(); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_profile); + ButterKnife.bind(this); + EventBus.getDefault().register(this); + applyCustomTheme(); + adjustToolbar(toolbar); + setSupportActionBar(toolbar); + + if (mSharedPreferences.getBoolean(SharedPreferencesUtils.SWIPE_RIGHT_TO_GO_BACK, true)) { + SlidrInterface slidrInterface = Slidr.attach(this); + slidrInterface.unlock(); + } + + mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); + mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_NAME, null); + + changeBanner.setOnClickListener(view -> { + startPickImage(PICK_IMAGE_BANNER_REQUEST_CODE); + }); + changeAvatar.setOnClickListener(view -> { + startPickImage(PICK_IMAGE_AVATAR_REQUEST_CODE); + }); + + final RequestManager glide = Glide.with(this); + final UserViewModel.Factory userViewModelFactory = + new UserViewModel.Factory(getApplication(), mRedditDataRoomDatabase, mAccountName); + final UserViewModel userViewModel = + new ViewModelProvider(this, userViewModelFactory).get(UserViewModel.class); + + userViewModel.getUserLiveData().observe(this, userData -> { + if (userData == null) return;// + // BANNER + final String userBanner = userData.getBanner(); + LayoutParams cBannerLp = (LayoutParams) changeBanner.getLayoutParams(); + if (userBanner == null || userBanner.isEmpty()) { + changeBanner.setLongClickable(false); + changeBanner.setImageResource(R.drawable.ic_add_day_night_24dp); + changeBanner.setLayoutParams(new LayoutParams(cBannerLp.width, cBannerLp.height, Gravity.CENTER)); + changeBanner.setOnLongClickListener(v -> false); + } else { + changeBanner.setLongClickable(true); + changeBanner.setImageResource(R.drawable.ic_outline_add_a_photo_day_night_24dp); + changeBanner.setLayoutParams(new LayoutParams(cBannerLp.width, cBannerLp.height, Gravity.END | Gravity.BOTTOM)); + glide.load(userBanner).into(bannerImageView); + changeBanner.setOnLongClickListener(view -> { + if (mAccessToken == null) return false; + new MaterialAlertDialogBuilder(this, R.style.MaterialAlertDialogTheme) + .setTitle(R.string.remove_banner) + .setMessage(R.string.are_you_sure) + .setPositiveButton(R.string.yes, (dialogInterface, i) + -> EditProfileUtils.deleteBanner(mOauthRetrofit, + mAccessToken, + mAccountName, + new EditProfileUtils.EditProfileUtilsListener() { + @Override + public void success() { + Toast.makeText(EditProfileActivity.this, + R.string.message_remove_banner_success, + Toast.LENGTH_SHORT).show(); + bannerImageView.setImageDrawable(null);// + } + + @Override + public void failed(String message) { + Toast.makeText(EditProfileActivity.this, + getString(R.string.message_remove_banner_failed_fmt, message), + Toast.LENGTH_SHORT).show(); + } + })) + .setNegativeButton(R.string.no, null) + .show(); + return true; + }); + } + // AVATAR + final String userAvatar = userData.getIconUrl(); + glide.load(userAvatar) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(216, 0))) + .into(avatarImageView); + LayoutParams cAvatarLp = (LayoutParams) changeAvatar.getLayoutParams(); + if (userAvatar.contains("avatar_default_")) { + changeAvatar.setLongClickable(false); + changeAvatar.setImageResource(R.drawable.ic_add_day_night_24dp); + changeAvatar.setLayoutParams(new LayoutParams(cAvatarLp.width, cAvatarLp.height, Gravity.CENTER)); + changeAvatar.setOnLongClickListener(v -> false); + } else { + changeAvatar.setLongClickable(true); + changeAvatar.setImageResource(R.drawable.ic_outline_add_a_photo_day_night_24dp); + changeAvatar.setLayoutParams(new LayoutParams(cAvatarLp.width, cAvatarLp.height, Gravity.END | Gravity.BOTTOM)); + changeAvatar.setOnLongClickListener(view -> { + if (mAccessToken == null) return false; + new MaterialAlertDialogBuilder(this, R.style.MaterialAlertDialogTheme) + .setTitle(R.string.remove_avatar) + .setMessage(R.string.are_you_sure) + .setPositiveButton(R.string.yes, (dialogInterface, i) + -> EditProfileUtils.deleteAvatar(mOauthRetrofit, + mAccessToken, + mAccountName, + new EditProfileUtils.EditProfileUtilsListener() { + @Override + public void success() { + Toast.makeText(EditProfileActivity.this, + R.string.message_remove_avatar_success, + Toast.LENGTH_SHORT).show();// + } + + @Override + public void failed(String message) { + Toast.makeText(EditProfileActivity.this, + getString(R.string.message_remove_avatar_failed_fmt, message), + Toast.LENGTH_SHORT).show(); + } + })) + .setNegativeButton(R.string.no, null) + .show(); + return true; + }); + } + + editTextAboutYou.setText(userData.getDescription()); + editTextDisplayName.setText(userData.getTitle()); + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK || data == null) return; // + if (mAccessToken == null || mAccountName == null) return; // + Intent intent = new Intent(this, EditProfileService.class); + intent.setData(data.getData()); + intent.putExtra(EditProfileService.EXTRA_ACCOUNT_NAME, mAccountName); + intent.putExtra(EditProfileService.EXTRA_ACCESS_TOKEN, mAccessToken); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + switch (requestCode) { + case PICK_IMAGE_BANNER_REQUEST_CODE: + intent.putExtra(EditProfileService.EXTRA_POST_TYPE, EditProfileService.EXTRA_POST_TYPE_CHANGE_BANNER); + ContextCompat.startForegroundService(this, intent); + break; + case PICK_IMAGE_AVATAR_REQUEST_CODE: + intent.putExtra(EditProfileService.EXTRA_POST_TYPE, EditProfileService.EXTRA_POST_TYPE_CHANGE_AVATAR); + ContextCompat.startForegroundService(this, intent); + break; + default: + break; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.edit_profile_activity, menu); + applyMenuItemTheme(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } else if (itemId == R.id.action_save_edit_profile_activity) { + String displayName = null; + if (editTextDisplayName.getText() != null) { + displayName = editTextDisplayName.getText().toString(); + } + String aboutYou = null; + if (editTextAboutYou.getText() != null) { + aboutYou = editTextAboutYou.getText().toString(); + } + if (aboutYou == null || displayName == null) return false; // + + Intent intent = new Intent(this, EditProfileService.class); + intent.putExtra(EditProfileService.EXTRA_ACCOUNT_NAME, mAccountName); + intent.putExtra(EditProfileService.EXTRA_ACCESS_TOKEN, mAccessToken); + intent.putExtra(EditProfileService.EXTRA_DISPLAY_NAME, displayName); // + intent.putExtra(EditProfileService.EXTRA_ABOUT_YOU, aboutYou); // + intent.putExtra(EditProfileService.EXTRA_POST_TYPE, EditProfileService.EXTRA_POST_TYPE_SAVE_EDIT_PROFILE); + + ContextCompat.startForegroundService(this, intent); + return true; + } + return false; + } + + @Subscribe + public void onSubmitChangeAvatar(SubmitChangeAvatarEvent event) { + if (event.isSuccess) { + Toast.makeText(this, R.string.message_change_avatar_success, Toast.LENGTH_SHORT).show(); + } else { + String message = getString(R.string.message_change_avatar_failed_fmt, event.errorMessage); + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + } + + @Subscribe + public void onSubmitChangeBanner(SubmitChangeBannerEvent event) { + if (event.isSuccess) { + Toast.makeText(this, R.string.message_change_banner_success, Toast.LENGTH_SHORT).show(); + } else { + String message = getString(R.string.message_change_banner_failed_fmt, event.errorMessage); + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + } + + @Subscribe + public void onSubmitSaveProfile(SubmitSaveProfileEvent event) { + if (event.isSuccess) { + Toast.makeText(this, R.string.message_save_profile_success, Toast.LENGTH_SHORT).show(); + } else { + String message = getString(R.string.message_save_profile_failed_fmt, event.errorMessage); + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + } + + + @Override + protected SharedPreferences getDefaultSharedPreferences() { + return mSharedPreferences; + } + + @Override + protected CustomThemeWrapper getCustomThemeWrapper() { + return mCustomThemeWrapper; + } + + @Override + protected void applyCustomTheme() { // + applyAppBarLayoutAndToolbarTheme(appBarLayout, toolbar); + root.setBackgroundColor(mCustomThemeWrapper.getBackgroundColor()); + changeColorTextView(content, mCustomThemeWrapper.getPrimaryTextColor()); + } + + private void changeColorTextView(ViewGroup viewGroup, int color) { + final int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = viewGroup.getChildAt(i); + if (child instanceof ViewGroup) { + changeColorTextView((ViewGroup) child, color); + } else if (child instanceof TextView) { + ((TextView) child).setTextColor(color); + } + } + } + + private void startPickImage(int requestId) { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + startActivityForResult( + Intent.createChooser(intent, getString(R.string.select_from_gallery)), + requestId); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewUserDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewUserDetailActivity.java index 7aa21f7f..dfc40140 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewUserDetailActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewUserDetailActivity.java @@ -1022,6 +1022,13 @@ public class ViewUserDetailActivity extends BaseActivity implements SortTypeSele @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.view_user_detail_activity, menu); + if(mAccountName.equals(username)){ // Hide some menus + menu.findItem(R.id.action_send_private_message_view_user_detail_activity).setVisible(false); + menu.findItem(R.id.action_report_view_user_detail_activity).setVisible(false); + menu.findItem(R.id.action_block_user_view_user_detail_activity).setVisible(false); + }else { // Hide Edit Profile menu + menu.findItem(R.id.action_edit_profile_view_user_detail_activity).setVisible(false); + } applyMenuItemTheme(menu); return true; } @@ -1110,6 +1117,9 @@ public class ViewUserDetailActivity extends BaseActivity implements SortTypeSele .setNegativeButton(R.string.no, null) .show(); return true; + } else if(itemId == R.id.action_edit_profile_view_user_detail_activity){ + startActivity(new Intent(this, EditProfileActivity.class)); + return true; } return false; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java index dd9efb33..4b00f4be 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java @@ -403,4 +403,24 @@ public interface RedditAPI { @GET("{multipath}.json?raw_json=1") ListenableFuture> getMultiRedditPostsOauthListenableFuture(@Path(value = "multipath", encoded = true) String multiPath, @Query("after") String after, @HeaderMap Map headers); + + @POST("r/{subredditName}/api/delete_sr_icon") + Call deleteSrIcon(@HeaderMap Map headers, @Path("subredditName") String subredditName); + + @POST("r/{subredditName}/api/delete_sr_banner") + Call deleteSrBanner(@HeaderMap Map headers, @Path("subredditName") String subredditName); + + @Multipart + @POST("r/{subredditName}/api/upload_sr_img") + Call uploadSrImg(@HeaderMap Map headers, + @Path("subredditName") String subredditName, + @PartMap Map params, + @Part MultipartBody.Part file); + + @GET("r/{subredditName}/about/edit?raw_json=1") + Call getSubredditSetting(@HeaderMap Map headers, @Path("subredditName") String subredditName); + + @FormUrlEncoded + @POST("/api/site_admin") + Call postSiteAdmin(@HeaderMap Map headers, @FieldMap Map params); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeAvatarEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeAvatarEvent.java new file mode 100644 index 00000000..3e806034 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeAvatarEvent.java @@ -0,0 +1,11 @@ +package ml.docilealligator.infinityforreddit.events; + +public class SubmitChangeAvatarEvent { + public final boolean isSuccess; + public final String errorMessage; + + public SubmitChangeAvatarEvent(boolean isSuccess, String errorMessage) { + this.isSuccess = isSuccess; + this.errorMessage = errorMessage; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeBannerEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeBannerEvent.java new file mode 100644 index 00000000..290445e3 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitChangeBannerEvent.java @@ -0,0 +1,11 @@ +package ml.docilealligator.infinityforreddit.events; + +public class SubmitChangeBannerEvent { + public final boolean isSuccess; + public final String errorMessage; + + public SubmitChangeBannerEvent(boolean isSuccess, String errorMessage) { + this.isSuccess = isSuccess; + this.errorMessage = errorMessage; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitSaveProfileEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitSaveProfileEvent.java new file mode 100644 index 00000000..d2ca5516 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/events/SubmitSaveProfileEvent.java @@ -0,0 +1,11 @@ +package ml.docilealligator.infinityforreddit.events; + +public class SubmitSaveProfileEvent { + public final boolean isSuccess; + public final String errorMessage; + + public SubmitSaveProfileEvent(boolean isSuccess, String errorMessage) { + this.isSuccess = isSuccess; + this.errorMessage = errorMessage; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/services/EditProfileService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/services/EditProfileService.java new file mode 100644 index 00000000..e93d16d2 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/services/EditProfileService.java @@ -0,0 +1,260 @@ +package ml.docilealligator.infinityforreddit.services; + +import android.app.Notification; +import android.app.Service; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import com.bumptech.glide.Glide; +import jp.wasabeef.glide.transformations.CropTransformation; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.events.SubmitChangeAvatarEvent; +import ml.docilealligator.infinityforreddit.events.SubmitChangeBannerEvent; +import ml.docilealligator.infinityforreddit.events.SubmitSaveProfileEvent; +import ml.docilealligator.infinityforreddit.utils.EditProfileUtils; +import ml.docilealligator.infinityforreddit.utils.NotificationUtils; +import org.greenrobot.eventbus.EventBus; +import retrofit2.Retrofit; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.FileNotFoundException; +import java.util.Random; +import java.util.concurrent.ExecutionException; + +public class EditProfileService extends Service { + public static final String EXTRA_ACCESS_TOKEN = "EAT"; + public static final String EXTRA_ACCOUNT_NAME = "EAN"; + public static final String EXTRA_DISPLAY_NAME = "EDN"; + public static final String EXTRA_ABOUT_YOU = "EAY"; + public static final String EXTRA_POST_TYPE = "EPT"; + + public static final int EXTRA_POST_TYPE_UNKNOWN = 0x500; + public static final int EXTRA_POST_TYPE_CHANGE_BANNER = 0x501; + public static final int EXTRA_POST_TYPE_CHANGE_AVATAR = 0x502; + public static final int EXTRA_POST_TYPE_SAVE_EDIT_PROFILE = 0x503; + + private static final String EXTRA_MEDIA_URI = "EU"; + + private static final int MAX_BANNER_WIDTH = 1280; + private static final int MIN_BANNER_WIDTH = 640; + private static final int AVATAR_SIZE = 256; + @Inject + @Named("oauth") + Retrofit mOauthRetrofit; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + private Handler handler; + private ServiceHandler serviceHandler; + + @Override + public void onCreate() { + super.onCreate(); + ((Infinity) getApplication()).getAppComponent().inject(this); + handler = new Handler(); + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + serviceHandler = new ServiceHandler(thread.getLooper()); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + ((Infinity) getApplication()).getAppComponent().inject(this); + + NotificationChannelCompat serviceChannel = + new NotificationChannelCompat.Builder( + NotificationUtils.CHANNEL_SUBMIT_POST, + NotificationManagerCompat.IMPORTANCE_LOW) + .setName(NotificationUtils.CHANNEL_SUBMIT_POST) + .build(); + + NotificationManagerCompat manager = NotificationManagerCompat.from(this); + manager.createNotificationChannel(serviceChannel); + + int randomNotificationIdOffset = new Random().nextInt(10000); + Bundle bundle = intent.getExtras(); + final int postType = intent.getIntExtra(EXTRA_POST_TYPE, EXTRA_POST_TYPE_UNKNOWN); + switch (postType) { + case EXTRA_POST_TYPE_CHANGE_BANNER: + bundle.putString(EXTRA_MEDIA_URI, intent.getData().toString()); + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(R.string.submit_change_banner)); + break; + case EXTRA_POST_TYPE_CHANGE_AVATAR: + bundle.putString(EXTRA_MEDIA_URI, intent.getData().toString()); + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(R.string.submit_change_avatar)); + break; + case EXTRA_POST_TYPE_SAVE_EDIT_PROFILE: + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(R.string.submit_save_profile)); + break; + default: + case EXTRA_POST_TYPE_UNKNOWN: + break; + } + + Message msg = serviceHandler.obtainMessage(); + msg.setData(bundle); + serviceHandler.sendMessage(msg); + return START_NOT_STICKY; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private void submitChangeBanner(String accessToken, Uri mediaUri, String accountName) { + try { + final int width = getWidthBanner(mediaUri); + final int height = Math.round(width * 3 / 10f); // ratio 10:3 + CropTransformation bannerCrop = new CropTransformation(width, height, CropTransformation.CropType.CENTER); + Bitmap resource = Glide.with(this).asBitmap().skipMemoryCache(true) + .load(mediaUri).transform(bannerCrop).submit().get(); + EditProfileUtils.uploadBanner(mOauthRetrofit, accessToken, accountName, resource, new EditProfileUtils.EditProfileUtilsListener() { + @Override + public void success() { + handler.post(() -> EventBus.getDefault().post(new SubmitChangeBannerEvent(true, ""))); + stopService(); + } + + @Override + public void failed(String message) { + handler.post(() -> EventBus.getDefault().post(new SubmitChangeBannerEvent(false, message))); + stopService(); + } + }); + } catch (InterruptedException | ExecutionException | FileNotFoundException e) { + e.printStackTrace(); + stopService(); + } + } + + private void submitChangeAvatar(String accessToken, Uri mediaUri, String accountName) { + try { + final CropTransformation avatarCrop = new CropTransformation(AVATAR_SIZE, AVATAR_SIZE, CropTransformation.CropType.CENTER); + final Bitmap resource = Glide.with(this).asBitmap().skipMemoryCache(true) + .load(mediaUri).transform(avatarCrop).submit().get(); + EditProfileUtils.uploadAvatar(mOauthRetrofit, accessToken, accountName, resource, new EditProfileUtils.EditProfileUtilsListener() { + @Override + public void success() { + handler.post(() -> EventBus.getDefault().post(new SubmitChangeAvatarEvent(true, ""))); + stopService(); + } + + @Override + public void failed(String message) { + handler.post(() -> EventBus.getDefault().post(new SubmitChangeAvatarEvent(false, message))); + stopService(); + } + }); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + stopService(); + } + } + + private void submitSaveEditProfile(String accessToken, + String accountName, + String displayName, + String publicDesc + ) { + EditProfileUtils.updateProfile(mOauthRetrofit, + accessToken, + accountName, + displayName, + publicDesc, + new EditProfileUtils.EditProfileUtilsListener() { + @Override + public void success() { + handler.post(() -> EventBus.getDefault().post(new SubmitSaveProfileEvent(true, ""))); + stopService(); + } + + @Override + public void failed(String message) { + handler.post(() -> EventBus.getDefault().post(new SubmitSaveProfileEvent(false, message))); + stopService(); + } + }); + + } + + private Notification createNotification(int stringResId) { + return new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_SUBMIT_POST) + .setContentTitle(getString(stringResId)) + .setContentText(getString(R.string.please_wait)) + .setSmallIcon(R.drawable.ic_notification) + .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) + .build(); + } + + private void stopService() { + stopForeground(true); + stopSelf(); + } + + + private int getWidthBanner(Uri mediaUri) throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(getContentResolver().openInputStream(mediaUri), null, options); + return Math.max(Math.min(options.outWidth, MAX_BANNER_WIDTH), MIN_BANNER_WIDTH); + } + + private class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + Bundle bundle = msg.getData(); + String accessToken = bundle.getString(EXTRA_ACCESS_TOKEN); + String accountName = bundle.getString(EXTRA_ACCOUNT_NAME); + final int postType = bundle.getInt(EXTRA_POST_TYPE, EXTRA_POST_TYPE_UNKNOWN); + switch (postType) { + case EXTRA_POST_TYPE_CHANGE_BANNER: + submitChangeBanner(accessToken, + Uri.parse(bundle.getString(EXTRA_MEDIA_URI)), + accountName); + break; + case EXTRA_POST_TYPE_CHANGE_AVATAR: + submitChangeAvatar(accessToken, + Uri.parse(bundle.getString(EXTRA_MEDIA_URI)), + accountName); + break; + case EXTRA_POST_TYPE_SAVE_EDIT_PROFILE: + submitSaveEditProfile( + accessToken, + accountName, + bundle.getString(EXTRA_DISPLAY_NAME), + bundle.getString(EXTRA_ABOUT_YOU) + ); + break; + default: + case EXTRA_POST_TYPE_UNKNOWN: + break; + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/subreddit/SubredditSettingData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/subreddit/SubredditSettingData.java new file mode 100644 index 00000000..4759d102 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/subreddit/SubredditSettingData.java @@ -0,0 +1,634 @@ +package ml.docilealligator.infinityforreddit.subreddit; + +import androidx.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + +import java.util.Objects; + +public class SubredditSettingData { + // Content visibility || Posts to this profile can appear in r/all and your profile can be discovered in /users + @SerializedName("default_set") + private boolean defaultSet; + @SerializedName("toxicity_threshold_chat_level") + private int toxicityThresholdChatLevel; + @SerializedName("crowd_control_chat_level") + private int crowdControlChatLevel; + @SerializedName("restrict_posting") + private boolean restrictPosting; + @SerializedName("public_description") + private String publicDescription; + @SerializedName("subreddit_id") + private String subredditId; + @SerializedName("allow_images") + private boolean allowImages; + @SerializedName("free_form_reports") + private boolean freeFormReports; + @SerializedName("domain") + @Nullable + private String domain; + @SerializedName("show_media") + private boolean showMedia; + @SerializedName("wiki_edit_age") + private int wikiEditAge; + @SerializedName("submit_text") + private String submitText; + @SerializedName("allow_polls") + private boolean allowPolls; + @SerializedName("title") + private String title; + @SerializedName("collapse_deleted_comments") + private boolean collapseDeletedComments; + @SerializedName("wikimode") + private String wikiMode; + @SerializedName("should_archive_posts") + private boolean shouldArchivePosts; + @SerializedName("allow_videos") + private boolean allowVideos; + @SerializedName("allow_galleries") + private boolean allowGalleries; + @SerializedName("crowd_control_level") + private int crowdControlLevel; + @SerializedName("crowd_control_mode") + private boolean crowdControlMode; + @SerializedName("welcome_message_enabled") + private boolean welcomeMessageEnabled; + @SerializedName("welcome_message_text") + @Nullable + private String welcomeMessageText; + @SerializedName("over_18") + private boolean over18; + @SerializedName("suggested_comment_sort") + private String suggestedCommentSort; + @SerializedName("disable_contributor_requests") + private boolean disableContributorRequests; + @SerializedName("original_content_tag_enabled") + private boolean originalContentTagEnabled; + @SerializedName("description") + private String description; + @SerializedName("submit_link_label") + private String submitLinkLabel; + @SerializedName("spoilers_enabled") + private boolean spoilersEnabled; + @SerializedName("allow_post_crossposts") + private boolean allowPostCrossPosts; + @SerializedName("spam_comments") + private String spamComments; + @SerializedName("public_traffic") + private boolean publicTraffic; + @SerializedName("restrict_commenting") + private boolean restrictCommenting; + @SerializedName("new_pinned_post_pns_enabled") + private boolean newPinnedPostPnsEnabled; + @SerializedName("submit_text_label") + private String submitTextLabel; + @SerializedName("all_original_content") + private boolean allOriginalContent; + @SerializedName("spam_selfposts") + private String spamSelfPosts; + @SerializedName("key_color") + private String keyColor; + @SerializedName("language") + private String language; + @SerializedName("wiki_edit_karma") + private int wikiEditKarma; + @SerializedName("hide_ads") + private boolean hideAds; + @SerializedName("prediction_leaderboard_entry_type") + private int predictionLeaderboardEntryType; + @SerializedName("header_hover_text") + private String headerHoverText; + @SerializedName("allow_chat_post_creation") + private boolean allowChatPostCreation; + @SerializedName("allow_prediction_contributors") + private boolean allowPredictionContributors; + @SerializedName("allow_discovery") + private boolean allowDiscovery; + @SerializedName("accept_followers") + private boolean acceptFollowers; + @SerializedName("exclude_banned_modqueue") + private boolean excludeBannedModQueue; + @SerializedName("allow_predictions_tournament") + private boolean allowPredictionsTournament; + @SerializedName("show_media_preview") + private boolean showMediaPreview; + @SerializedName("comment_score_hide_mins") + private int commentScoreHideMins; + @SerializedName("subreddit_type") + private String subredditType; + @SerializedName("spam_links") + private String spamLinks; + @SerializedName("allow_predictions") + private boolean allowPredictions; + @SerializedName("user_flair_pns_enabled") + private boolean userFlairPnsEnabled; + @SerializedName("content_options") + private String contentOptions; + + public boolean isDefaultSet() { + return defaultSet; + } + + public void setDefaultSet(boolean defaultSet) { + this.defaultSet = defaultSet; + } + + public int getToxicityThresholdChatLevel() { + return toxicityThresholdChatLevel; + } + + public void setToxicityThresholdChatLevel(int toxicityThresholdChatLevel) { + this.toxicityThresholdChatLevel = toxicityThresholdChatLevel; + } + + public int getCrowdControlChatLevel() { + return crowdControlChatLevel; + } + + public void setCrowdControlChatLevel(int crowdControlChatLevel) { + this.crowdControlChatLevel = crowdControlChatLevel; + } + + public boolean isRestrictPosting() { + return restrictPosting; + } + + public void setRestrictPosting(boolean restrictPosting) { + this.restrictPosting = restrictPosting; + } + + public String getPublicDescription() { + return publicDescription; + } + + public void setPublicDescription(String publicDescription) { + this.publicDescription = publicDescription; + } + + public String getSubredditId() { + return subredditId; + } + + public void setSubredditId(String subredditId) { + this.subredditId = subredditId; + } + + public boolean isAllowImages() { + return allowImages; + } + + public void setAllowImages(boolean allowImages) { + this.allowImages = allowImages; + } + + public boolean isFreeFormReports() { + return freeFormReports; + } + + public void setFreeFormReports(boolean freeFormReports) { + this.freeFormReports = freeFormReports; + } + + @Nullable + public String getDomain() { + return domain; + } + + public void setDomain(@Nullable String domain) { + this.domain = domain; + } + + public boolean isShowMedia() { + return showMedia; + } + + public void setShowMedia(boolean showMedia) { + this.showMedia = showMedia; + } + + public int getWikiEditAge() { + return wikiEditAge; + } + + public void setWikiEditAge(int wikiEditAge) { + this.wikiEditAge = wikiEditAge; + } + + public String getSubmitText() { + return submitText; + } + + public void setSubmitText(String submitText) { + this.submitText = submitText; + } + + public boolean isAllowPolls() { + return allowPolls; + } + + public void setAllowPolls(boolean allowPolls) { + this.allowPolls = allowPolls; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isCollapseDeletedComments() { + return collapseDeletedComments; + } + + public void setCollapseDeletedComments(boolean collapseDeletedComments) { + this.collapseDeletedComments = collapseDeletedComments; + } + + public String getWikiMode() { + return wikiMode; + } + + public void setWikiMode(String wikiMode) { + this.wikiMode = wikiMode; + } + + public boolean isShouldArchivePosts() { + return shouldArchivePosts; + } + + public void setShouldArchivePosts(boolean shouldArchivePosts) { + this.shouldArchivePosts = shouldArchivePosts; + } + + public boolean isAllowVideos() { + return allowVideos; + } + + public void setAllowVideos(boolean allowVideos) { + this.allowVideos = allowVideos; + } + + public boolean isAllowGalleries() { + return allowGalleries; + } + + public void setAllowGalleries(boolean allowGalleries) { + this.allowGalleries = allowGalleries; + } + + public int getCrowdControlLevel() { + return crowdControlLevel; + } + + public void setCrowdControlLevel(int crowdControlLevel) { + this.crowdControlLevel = crowdControlLevel; + } + + public boolean isCrowdControlMode() { + return crowdControlMode; + } + + public void setCrowdControlMode(boolean crowdControlMode) { + this.crowdControlMode = crowdControlMode; + } + + public boolean isWelcomeMessageEnabled() { + return welcomeMessageEnabled; + } + + public void setWelcomeMessageEnabled(boolean welcomeMessageEnabled) { + this.welcomeMessageEnabled = welcomeMessageEnabled; + } + + @Nullable + public String getWelcomeMessageText() { + return welcomeMessageText; + } + + public void setWelcomeMessageText(@Nullable String welcomeMessageText) { + this.welcomeMessageText = welcomeMessageText; + } + + public boolean isOver18() { + return over18; + } + + public void setOver18(boolean over18) { + this.over18 = over18; + } + + public String getSuggestedCommentSort() { + return suggestedCommentSort; + } + + public void setSuggestedCommentSort(String suggestedCommentSort) { + this.suggestedCommentSort = suggestedCommentSort; + } + + public boolean isDisableContributorRequests() { + return disableContributorRequests; + } + + public void setDisableContributorRequests(boolean disableContributorRequests) { + this.disableContributorRequests = disableContributorRequests; + } + + public boolean isOriginalContentTagEnabled() { + return originalContentTagEnabled; + } + + public void setOriginalContentTagEnabled(boolean originalContentTagEnabled) { + this.originalContentTagEnabled = originalContentTagEnabled; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getSubmitLinkLabel() { + return submitLinkLabel; + } + + public void setSubmitLinkLabel(String submitLinkLabel) { + this.submitLinkLabel = submitLinkLabel; + } + + public boolean isSpoilersEnabled() { + return spoilersEnabled; + } + + public void setSpoilersEnabled(boolean spoilersEnabled) { + this.spoilersEnabled = spoilersEnabled; + } + + public boolean isAllowPostCrossPosts() { + return allowPostCrossPosts; + } + + public void setAllowPostCrossPosts(boolean allowPostCrossPosts) { + this.allowPostCrossPosts = allowPostCrossPosts; + } + + public String getSpamComments() { + return spamComments; + } + + public void setSpamComments(String spamComments) { + this.spamComments = spamComments; + } + + public boolean isPublicTraffic() { + return publicTraffic; + } + + public void setPublicTraffic(boolean publicTraffic) { + this.publicTraffic = publicTraffic; + } + + public boolean isRestrictCommenting() { + return restrictCommenting; + } + + public void setRestrictCommenting(boolean restrictCommenting) { + this.restrictCommenting = restrictCommenting; + } + + public boolean isNewPinnedPostPnsEnabled() { + return newPinnedPostPnsEnabled; + } + + public void setNewPinnedPostPnsEnabled(boolean newPinnedPostPnsEnabled) { + this.newPinnedPostPnsEnabled = newPinnedPostPnsEnabled; + } + + public String getSubmitTextLabel() { + return submitTextLabel; + } + + public void setSubmitTextLabel(String submitTextLabel) { + this.submitTextLabel = submitTextLabel; + } + + public boolean isAllOriginalContent() { + return allOriginalContent; + } + + public void setAllOriginalContent(boolean allOriginalContent) { + this.allOriginalContent = allOriginalContent; + } + + public String getSpamSelfPosts() { + return spamSelfPosts; + } + + public void setSpamSelfPosts(String spamSelfPosts) { + this.spamSelfPosts = spamSelfPosts; + } + + public String getKeyColor() { + return keyColor; + } + + public void setKeyColor(String keyColor) { + this.keyColor = keyColor; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public int getWikiEditKarma() { + return wikiEditKarma; + } + + public void setWikiEditKarma(int wikiEditKarma) { + this.wikiEditKarma = wikiEditKarma; + } + + public boolean isHideAds() { + return hideAds; + } + + public void setHideAds(boolean hideAds) { + this.hideAds = hideAds; + } + + public int getPredictionLeaderboardEntryType() { + return predictionLeaderboardEntryType; + } + + public void setPredictionLeaderboardEntryType(int predictionLeaderboardEntryType) { + this.predictionLeaderboardEntryType = predictionLeaderboardEntryType; + } + + public String getHeaderHoverText() { + return headerHoverText; + } + + public void setHeaderHoverText(String headerHoverText) { + this.headerHoverText = headerHoverText; + } + + public boolean isAllowChatPostCreation() { + return allowChatPostCreation; + } + + public void setAllowChatPostCreation(boolean allowChatPostCreation) { + this.allowChatPostCreation = allowChatPostCreation; + } + + public boolean isAllowPredictionContributors() { + return allowPredictionContributors; + } + + public void setAllowPredictionContributors(boolean allowPredictionContributors) { + this.allowPredictionContributors = allowPredictionContributors; + } + + public boolean isAllowDiscovery() { + return allowDiscovery; + } + + public void setAllowDiscovery(boolean allowDiscovery) { + this.allowDiscovery = allowDiscovery; + } + + public boolean isAcceptFollowers() { + return acceptFollowers; + } + + public void setAcceptFollowers(boolean acceptFollowers) { + this.acceptFollowers = acceptFollowers; + } + + public boolean isExcludeBannedModQueue() { + return excludeBannedModQueue; + } + + public void setExcludeBannedModQueue(boolean excludeBannedModQueue) { + this.excludeBannedModQueue = excludeBannedModQueue; + } + + public boolean isAllowPredictionsTournament() { + return allowPredictionsTournament; + } + + public void setAllowPredictionsTournament(boolean allowPredictionsTournament) { + this.allowPredictionsTournament = allowPredictionsTournament; + } + + public boolean isShowMediaPreview() { + return showMediaPreview; + } + + public void setShowMediaPreview(boolean showMediaPreview) { + this.showMediaPreview = showMediaPreview; + } + + public int getCommentScoreHideMins() { + return commentScoreHideMins; + } + + public void setCommentScoreHideMins(int commentScoreHideMins) { + this.commentScoreHideMins = commentScoreHideMins; + } + + public String getSubredditType() { + return subredditType; + } + + public void setSubredditType(String subredditType) { + this.subredditType = subredditType; + } + + public String getSpamLinks() { + return spamLinks; + } + + public void setSpamLinks(String spamLinks) { + this.spamLinks = spamLinks; + } + + public boolean isAllowPredictions() { + return allowPredictions; + } + + public void setAllowPredictions(boolean allowPredictions) { + this.allowPredictions = allowPredictions; + } + + public boolean isUserFlairPnsEnabled() { + return userFlairPnsEnabled; + } + + public void setUserFlairPnsEnabled(boolean userFlairPnsEnabled) { + this.userFlairPnsEnabled = userFlairPnsEnabled; + } + + public String getContentOptions() { + return contentOptions; + } + + public void setContentOptions(String contentOptions) { + this.contentOptions = contentOptions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubredditSettingData that = (SubredditSettingData) o; + return defaultSet == that.defaultSet && toxicityThresholdChatLevel == that.toxicityThresholdChatLevel + && crowdControlChatLevel == that.crowdControlChatLevel && restrictPosting == that.restrictPosting + && allowImages == that.allowImages && freeFormReports == that.freeFormReports && showMedia == that.showMedia + && wikiEditAge == that.wikiEditAge && allowPolls == that.allowPolls && collapseDeletedComments == that.collapseDeletedComments + && shouldArchivePosts == that.shouldArchivePosts && allowVideos == that.allowVideos + && allowGalleries == that.allowGalleries && crowdControlLevel == that.crowdControlLevel + && crowdControlMode == that.crowdControlMode && welcomeMessageEnabled == that.welcomeMessageEnabled + && over18 == that.over18 && disableContributorRequests == that.disableContributorRequests + && originalContentTagEnabled == that.originalContentTagEnabled && spoilersEnabled == that.spoilersEnabled + && allowPostCrossPosts == that.allowPostCrossPosts && publicTraffic == that.publicTraffic + && restrictCommenting == that.restrictCommenting && newPinnedPostPnsEnabled == that.newPinnedPostPnsEnabled + && allOriginalContent == that.allOriginalContent && wikiEditKarma == that.wikiEditKarma + && hideAds == that.hideAds && predictionLeaderboardEntryType == that.predictionLeaderboardEntryType + && allowChatPostCreation == that.allowChatPostCreation && allowPredictionContributors == that.allowPredictionContributors + && allowDiscovery == that.allowDiscovery && acceptFollowers == that.acceptFollowers + && excludeBannedModQueue == that.excludeBannedModQueue && allowPredictionsTournament == that.allowPredictionsTournament + && showMediaPreview == that.showMediaPreview && commentScoreHideMins == that.commentScoreHideMins + && allowPredictions == that.allowPredictions && userFlairPnsEnabled == that.userFlairPnsEnabled + && Objects.equals(publicDescription, that.publicDescription) && Objects.equals(subredditId, that.subredditId) + && Objects.equals(domain, that.domain) && Objects.equals(submitText, that.submitText) + && Objects.equals(title, that.title) && Objects.equals(wikiMode, that.wikiMode) && + Objects.equals(welcomeMessageText, that.welcomeMessageText) && Objects.equals(suggestedCommentSort, that.suggestedCommentSort) + && Objects.equals(description, that.description) && Objects.equals(submitLinkLabel, that.submitLinkLabel) + && Objects.equals(spamComments, that.spamComments) && Objects.equals(submitTextLabel, that.submitTextLabel) + && Objects.equals(spamSelfPosts, that.spamSelfPosts) && Objects.equals(keyColor, that.keyColor) + && Objects.equals(language, that.language) && Objects.equals(headerHoverText, that.headerHoverText) + && Objects.equals(subredditType, that.subredditType) && Objects.equals(spamLinks, that.spamLinks) + && Objects.equals(contentOptions, that.contentOptions); + } + + @Override + public int hashCode() { + return Objects.hash(defaultSet, toxicityThresholdChatLevel, crowdControlChatLevel, restrictPosting, + publicDescription, subredditId, allowImages, freeFormReports, domain, showMedia, wikiEditAge, + submitText, allowPolls, title, collapseDeletedComments, wikiMode, shouldArchivePosts, + allowVideos, allowGalleries, crowdControlLevel, crowdControlMode, welcomeMessageEnabled, + welcomeMessageText, over18, suggestedCommentSort, disableContributorRequests, originalContentTagEnabled, + description, submitLinkLabel, spoilersEnabled, allowPostCrossPosts, spamComments, publicTraffic, + restrictCommenting, newPinnedPostPnsEnabled, submitTextLabel, allOriginalContent, spamSelfPosts, + keyColor, language, wikiEditKarma, hideAds, predictionLeaderboardEntryType, headerHoverText, + allowChatPostCreation, allowPredictionContributors, allowDiscovery, acceptFollowers, + excludeBannedModQueue, allowPredictionsTournament, showMediaPreview, commentScoreHideMins, + subredditType, spamLinks, allowPredictions, userFlairPnsEnabled, contentOptions); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/user/ParseUserData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/user/ParseUserData.java index 608f78a4..a57a98ed 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/user/ParseUserData.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/user/ParseUserData.java @@ -52,9 +52,10 @@ public class ParseUserData { boolean isFriend = userDataJson.getBoolean(JSONUtils.IS_FRIEND_KEY); boolean isNsfw = userDataJson.getJSONObject(JSONUtils.SUBREDDIT_KEY).getBoolean(JSONUtils.OVER_18_KEY); String description = userDataJson.getJSONObject(JSONUtils.SUBREDDIT_KEY).getString(JSONUtils.PUBLIC_DESCRIPTION_KEY); + String title = userDataJson.getJSONObject(JSONUtils.SUBREDDIT_KEY).getString(JSONUtils.TITLE_KEY); return new UserData(userName, iconImageUrl, bannerImageUrl, linkKarma, commentKarma, awarderKarma, - awardeeKarma, totalKarma, cakeday, isGold, isFriend, canBeFollowed, isNsfw, description); + awardeeKarma, totalKarma, cakeday, isGold, isFriend, canBeFollowed, isNsfw, description, title); } interface ParseUserDataListener { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/user/UserData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/user/UserData.java index 06be82f1..4512ca86 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/user/UserData.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/user/UserData.java @@ -38,12 +38,14 @@ public class UserData { private boolean isNSFW; @ColumnInfo(name = "description") private String description; + @ColumnInfo(name = "title") + private String title; @Ignore private boolean isSelected; public UserData(@NonNull String name, String iconUrl, String banner, int linkKarma, int commentKarma, int awarderKarma, int awardeeKarma, int totalKarma, long cakeday, boolean isGold, - boolean isFriend, boolean canBeFollowed, boolean isNSFW, String description) { + boolean isFriend, boolean canBeFollowed, boolean isNSFW, String description, String title) { this.name = name; this.iconUrl = iconUrl; this.banner = banner; @@ -58,6 +60,7 @@ public class UserData { this.canBeFollowed = canBeFollowed; this.isNSFW = isNSFW; this.description = description; + this.title = title; this.isSelected = false; } @@ -118,6 +121,10 @@ public class UserData { return description; } + public String getTitle() { + return title; + } + public boolean isSelected() { return isSelected; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/EditProfileUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/EditProfileUtils.java new file mode 100644 index 00000000..5293bc71 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/EditProfileUtils.java @@ -0,0 +1,276 @@ +package ml.docilealligator.infinityforreddit.utils; + +import android.graphics.Bitmap; +import androidx.annotation.NonNull; +import com.google.gson.Gson; +import ml.docilealligator.infinityforreddit.apis.RedditAPI; +import ml.docilealligator.infinityforreddit.subreddit.SubredditSettingData; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import org.json.JSONException; +import org.json.JSONObject; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; + +public final class EditProfileUtils { + + + + public static void updateProfile(Retrofit oauthRetrofit, + String accessToken, + String accountName, + String displayName, + String publicDesc, + EditProfileUtilsListener listener) { + final Map oauthHeader = APIUtils.getOAuthHeader(accessToken); + final RedditAPI api = oauthRetrofit.create(RedditAPI.class); + final String name = "u_" + accountName; + api.getSubredditSetting(oauthHeader, name).enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + try { + final String json = response.body(); + if (json == null) { + listener.failed("Something happen."); + return; + } + + final JSONObject resBody = new JSONObject(json); + final SubredditSettingData data = new Gson().fromJson(resBody.getString("data"), SubredditSettingData.class); + + if (data.getPublicDescription().equals(publicDesc) + && data.getTitle().equals(displayName)) { + // no-op + listener.success(); + return; + } + + final Map params = new HashMap<>(); + + params.put("api_type", "json"); + params.put("sr", data.getSubredditId()); + params.put("name", name); + params.put("type", data.getSubredditType()); + // Only this 2 param + params.put("public_description", publicDesc); + params.put("title", displayName); + // Official Reddit app have this 2 params + // 1 = disable; 0 = enable || Active in communities visibility || Show which communities I am active in on my profile. + params.put("toxicity_threshold_chat_level", String.valueOf(data.getToxicityThresholdChatLevel())); + // Content visibility || Posts to this profile can appear in r/all and your profile can be discovered in /users + params.put("default_set", String.valueOf(data.isDefaultSet())); + + // Allow people to follow you || Followers will be notified about posts you make to your profile and see them in their home feed. + params.put("accept_followers", String.valueOf(data.isAcceptFollowers())); + + params.put("allow_top", String.valueOf(data.isPublicTraffic())); // + params.put("link_type", String.valueOf(data.getContentOptions())); // + // + params.put("original_content_tag_enabled", String.valueOf(data.isOriginalContentTagEnabled())); + params.put("new_pinned_post_pns_enabled", String.valueOf(data.isNewPinnedPostPnsEnabled())); + params.put("prediction_leaderboard_entry_type", String.valueOf(data.getPredictionLeaderboardEntryType())); + params.put("restrict_commenting", String.valueOf(data.isRestrictCommenting())); + params.put("restrict_posting", String.valueOf(data.isRestrictPosting())); + params.put("should_archive_posts", String.valueOf(data.isShouldArchivePosts())); + params.put("show_media", String.valueOf(data.isShowMedia())); + params.put("show_media_preview", String.valueOf(data.isShowMediaPreview())); + params.put("spam_comments", data.getSpamComments()); + params.put("spam_links", data.getSpamLinks()); + params.put("spam_selfposts", data.getSpamSelfPosts()); + params.put("spoilers_enabled", String.valueOf(data.isSpoilersEnabled())); + params.put("submit_link_label", data.getSubmitLinkLabel()); + params.put("submit_text", data.getSubmitText()); + params.put("submit_text_label", data.getSubmitTextLabel()); + params.put("user_flair_pns_enabled", String.valueOf(data.isUserFlairPnsEnabled())); + params.put("all_original_content", String.valueOf(data.isAllOriginalContent())); + params.put("allow_chat_post_creation", String.valueOf(data.isAllowChatPostCreation())); + params.put("allow_discovery", String.valueOf(data.isAllowDiscovery())); + params.put("allow_galleries", String.valueOf(data.isAllowGalleries())); + params.put("allow_images", String.valueOf(data.isAllowImages())); + params.put("allow_polls", String.valueOf(data.isAllowPolls())); + params.put("allow_post_crossposts", String.valueOf(data.isAllowPostCrossPosts())); + params.put("allow_prediction_contributors", String.valueOf(data.isAllowPredictionContributors())); + params.put("allow_predictions", String.valueOf(data.isAllowPredictions())); + params.put("allow_predictions_tournament", String.valueOf(data.isAllowPredictionsTournament())); + params.put("allow_videos", String.valueOf(data.isAllowVideos())); + params.put("collapse_deleted_comments", String.valueOf(data.isCollapseDeletedComments())); + params.put("comment_score_hide_mins", String.valueOf(data.getCommentScoreHideMins())); + params.put("crowd_control_chat_level", String.valueOf(data.getCrowdControlChatLevel())); + params.put("crowd_control_filter", String.valueOf(data.getCrowdControlChatLevel())); + params.put("crowd_control_level", String.valueOf(data.getCrowdControlLevel())); + params.put("crowd_control_mode", String.valueOf(data.isCrowdControlMode())); + params.put("description", data.getDescription()); + params.put("disable_contributor_requests", String.valueOf(data.isDisableContributorRequests())); + params.put("exclude_banned_modqueue", String.valueOf(data.isExcludeBannedModQueue())); + params.put("free_form_reports", String.valueOf(data.isFreeFormReports())); + params.put("header-title", data.getHeaderHoverText()); + params.put("hide_ads", String.valueOf(data.isHideAds())); + params.put("key_color", data.getKeyColor()); + params.put("lang", data.getLanguage()); + params.put("over_18", String.valueOf(data.isOver18())); + params.put("suggested_comment_sort", data.getSuggestedCommentSort()); + params.put("welcome_message_enabled", String.valueOf(data.isWelcomeMessageEnabled())); + params.put("welcome_message_text", String.valueOf(data.getWelcomeMessageText())); + params.put("wiki_edit_age", String.valueOf(data.getWikiEditAge())); + params.put("wiki_edit_karma", String.valueOf(data.getWikiEditKarma())); + params.put("wikimode", data.getWikiMode()); + + api.postSiteAdmin(oauthHeader, params) + .enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) listener.success(); + else listener.failed(response.message()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + } catch (JSONException e) { + listener.failed(e.getLocalizedMessage()); + } + } else { + listener.failed(response.message()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + + } + + public static void uploadAvatar(Retrofit oauthRetrofit, + String accessToken, + String accountName, + Bitmap image, + EditProfileUtilsListener listener) { + oauthRetrofit.create(RedditAPI.class) + .uploadSrImg( + APIUtils.getOAuthHeader(accessToken), + "u_" + accountName, + requestBodyUploadSr("icon"), + fileToUpload(image, accountName + "-icon")) + .enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, + @NonNull Response response) { + if (response.isSuccessful()) listener.success(); + else listener.failed(response.message()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + } + + public static void uploadBanner(Retrofit oauthRetrofit, + String accessToken, + String accountName, + Bitmap image, + EditProfileUtilsListener listener) { + oauthRetrofit.create(RedditAPI.class) + .uploadSrImg( + APIUtils.getOAuthHeader(accessToken), + "u_" + accountName, + requestBodyUploadSr("banner"), + fileToUpload(image, accountName + "-banner")) + .enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, + @NonNull Response response) { + if (response.isSuccessful()) listener.success(); + else listener.failed(response.message()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + } + + public static void deleteAvatar(Retrofit oauthRetrofit, + String accessToken, + String accountName, + EditProfileUtilsListener listener) { + oauthRetrofit.create(RedditAPI.class) + .deleteSrIcon(APIUtils.getOAuthHeader(accessToken), "u_" + accountName) + .enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, + @NonNull Response response) { + if (response.isSuccessful()) listener.success(); + else listener.failed(response.message()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + } + + public static void deleteBanner(Retrofit oauthRetrofit, + String accessToken, + String accountName, + EditProfileUtilsListener listener) { + oauthRetrofit.create(RedditAPI.class) + .deleteSrBanner(APIUtils.getOAuthHeader(accessToken), "u_" + accountName) + .enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, + @NonNull Response response) { + if (response.isSuccessful()) listener.success(); + else listener.failed(response.message()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + listener.failed(t.getLocalizedMessage()); + } + }); + } + + private static Map requestBodyUploadSr(String type) { + Map param = new HashMap<>(); + param.put("upload_type", APIUtils.getRequestBody(type)); + param.put("img_type", APIUtils.getRequestBody("jpg")); + return param; + } + + private static MultipartBody.Part fileToUpload(Bitmap image, String fileName) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.JPEG, 100, stream); + byte[] byteArray = stream.toByteArray(); + RequestBody fileBody = RequestBody.create(byteArray, + MediaType.parse("image/*")); + return MultipartBody.Part.createFormData("file", fileName + ".jpg", fileBody); + } + + public interface EditProfileUtilsListener { + void success(); + + void failed(String message); + } +} diff --git a/app/src/main/res/drawable/ic_dot_outline.xml b/app/src/main/res/drawable/ic_dot_outline.xml new file mode 100644 index 00000000..dd9c5588 --- /dev/null +++ b/app/src/main/res/drawable/ic_dot_outline.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/layout/activity_edit_profile.xml b/app/src/main/res/layout/activity_edit_profile.xml new file mode 100644 index 00000000..6f472c9b --- /dev/null +++ b/app/src/main/res/layout/activity_edit_profile.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/edit_profile_activity.xml b/app/src/main/res/menu/edit_profile_activity.xml new file mode 100644 index 00000000..fe96ba20 --- /dev/null +++ b/app/src/main/res/menu/edit_profile_activity.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/menu/view_user_detail_activity.xml b/app/src/main/res/menu/view_user_detail_activity.xml index 437c6e23..ec9128e4 100644 --- a/app/src/main/res/menu/view_user_detail_activity.xml +++ b/app/src/main/res/menu/view_user_detail_activity.xml @@ -59,4 +59,9 @@ android:id="@+id/action_block_user_view_user_detail_activity" android:orderInCategory="10" android:title="@string/action_block_user" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a5890c7..3b8753e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1215,4 +1215,29 @@ 12 hours 24 hours + + Submit Change Avatar + Submit Change Banner + Submit Save Profile + + + Edit Profile + Remove Avatar + Remove Banner + Display Name + Show on your profile page + This will be displayed to viewer of your profile page and does not change your username + About You + A little description of your self + Success remove Avatar + Failed remove Avatar %s + Success remove Banner + Failed remove Banner %s + Success changing Avatar + Failed changing Avatar %s + Success changing Banner + Failed changing Banner %s + Success save profile + Failed save profile %s +