From 97ab6108cfaface4f17c8c92c0fece8806b76dbd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=BC=A0=E7=8E=89=E4=B9=85?= <1484288157@qq.com>
Date: Thu, 17 Sep 2020 18:08:01 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=B2=97=E7=95=A5=E7=9A=84?=
=?UTF-8?q?=E5=A4=B4=E5=83=8F=E6=8C=82=E4=BB=B6=E5=92=8C=E6=9B=B4=E6=8D=A2?=
=?UTF-8?q?=E8=83=8C=E6=99=AF=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/build.gradle | 3 +
app/src/main/AndroidManifest.xml | 17 +
.../java/com/gh/common/util/BitmapUtils.java | 132 +++++++-
.../java/com/gh/common/util/Extensions.kt | 21 +-
.../com/gh/common/util/UploadImageUtils.kt | 7 +-
.../com/gh/common/view/AvatarBorderView.kt | 115 +++++++
.../com/gh/common/view/CropImageCustom.java | 6 +
.../java/com/gh/common/view/CustomSeekBar.kt | 94 ++++++
.../com/gh/gamecenter/CropImageActivity.java | 3 +-
.../gamecenter/entity/AvatarBorderEntity.kt | 10 +
.../entity/BackgroundImageEntity.kt | 15 +
.../gh/gamecenter/entity/UserInfoEntity.kt | 5 +
.../forum/follow/ForumMyFollowFragment.kt | 3 +
.../background/BackgroundClipActivity.kt | 85 +++++
.../background/BackgroundPreviewActivity.kt | 40 +++
.../background/BackgroundPreviewFragment.kt | 291 ++++++++++++++++++
.../PersonalityBackgroundActivity.kt | 20 ++
.../PersonalityBackgroundAdapter.kt | 45 +++
.../PersonalityBackgroundFragment.kt | 114 +++++++
.../PersonalityBackgroundViewModel.kt | 68 ++++
.../border/AvatarBorderActivity.kt | 33 ++
.../border/AvatarBorderFragment.kt | 127 ++++++++
.../border/ChooseAvatarBorderAdapter.kt | 66 ++++
.../border/ChooseAvatarBorderFragment.kt | 76 +++++
.../border/ChooseAvatarBorderViewModel.kt | 37 +++
.../retrofit/service/ApiService.java | 17 +-
.../gh/gamecenter/user/UserRepository.java | 35 ++-
.../com/gh/gamecenter/user/UserViewModel.java | 27 +-
.../ic_avatar_border_select.png | Bin 0 -> 1873 bytes
.../res/drawable-xxhdpi/ic_background_add.png | Bin 0 -> 513 bytes
.../res/drawable-xxhdpi/ic_seekbar_thumb.png | Bin 0 -> 2137 bytes
.../drawable-xxxhdpi/bg_avatar_border.webp | Bin 0 -> 2418 bytes
.../res/drawable-xxxhdpi/preview_home.png | Bin 0 -> 140456 bytes
.../drawable-xxxhdpi/preview_home_full.png | Bin 0 -> 148024 bytes
.../res/drawable-xxxhdpi/preview_mine.png | Bin 0 -> 84437 bytes
.../drawable-xxxhdpi/preview_mine_full.png | Bin 0 -> 88031 bytes
.../background_shape_white_radius_4.xml | 6 +
.../drawable/bg_background_preview_top.xml | 8 +
.../bg_border_blue_radius_4_width_1.xml | 13 +
.../res/drawable/bg_shape_fa_radius_4.xml | 6 +
.../res/drawable/border_round_ccc_999.xml | 13 +
.../main/res/drawable/seekbar_progress.xml | 23 ++
.../res/layout/activity_background_clip.xml | 61 ++++
.../res/layout/avatar_background_item.xml | 58 ++++
app/src/main/res/layout/avatar_item.xml | 67 ++++
.../res/layout/fragment_avatar_border.xml | 184 +++++++++++
.../layout/fragment_background_preview.xml | 222 +++++++++++++
.../personality_background_fragment.xml | 80 +++++
app/src/main/res/values/attrs.xml | 122 ++++----
app/src/main/res/values/colors.xml | 2 +
50 files changed, 2301 insertions(+), 76 deletions(-)
create mode 100644 app/src/main/java/com/gh/common/view/AvatarBorderView.kt
create mode 100644 app/src/main/java/com/gh/common/view/CustomSeekBar.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/entity/AvatarBorderEntity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/entity/BackgroundImageEntity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundClipActivity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewActivity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewFragment.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundActivity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundAdapter.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundFragment.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundViewModel.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderActivity.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderFragment.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderAdapter.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderFragment.kt
create mode 100644 app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderViewModel.kt
create mode 100644 app/src/main/res/drawable-xxhdpi/ic_avatar_border_select.png
create mode 100644 app/src/main/res/drawable-xxhdpi/ic_background_add.png
create mode 100644 app/src/main/res/drawable-xxhdpi/ic_seekbar_thumb.png
create mode 100644 app/src/main/res/drawable-xxxhdpi/bg_avatar_border.webp
create mode 100644 app/src/main/res/drawable-xxxhdpi/preview_home.png
create mode 100644 app/src/main/res/drawable-xxxhdpi/preview_home_full.png
create mode 100644 app/src/main/res/drawable-xxxhdpi/preview_mine.png
create mode 100644 app/src/main/res/drawable-xxxhdpi/preview_mine_full.png
create mode 100644 app/src/main/res/drawable/background_shape_white_radius_4.xml
create mode 100644 app/src/main/res/drawable/bg_background_preview_top.xml
create mode 100644 app/src/main/res/drawable/bg_border_blue_radius_4_width_1.xml
create mode 100644 app/src/main/res/drawable/bg_shape_fa_radius_4.xml
create mode 100644 app/src/main/res/drawable/border_round_ccc_999.xml
create mode 100644 app/src/main/res/drawable/seekbar_progress.xml
create mode 100644 app/src/main/res/layout/activity_background_clip.xml
create mode 100644 app/src/main/res/layout/avatar_background_item.xml
create mode 100644 app/src/main/res/layout/avatar_item.xml
create mode 100644 app/src/main/res/layout/fragment_avatar_border.xml
create mode 100644 app/src/main/res/layout/fragment_background_preview.xml
create mode 100644 app/src/main/res/layout/personality_background_fragment.xml
diff --git a/app/build.gradle b/app/build.gradle
index ed911266c7..c9177790e5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -45,6 +45,9 @@ android {
abiFilters "armeabi-v7a"
}
+ renderscriptTargetApi 18
+ renderscriptSupportModeEnabled true
+
// 由于app只针对中文用户,所以仅保留zh资源,其他删掉
resConfigs "zh"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d9cfe052ff..c63abfff70 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -562,6 +562,23 @@
android:name=".video.label.VideoLabelActivity"
android:screenOrientation="portrait" />
+
+
+
+
+
+
+
+
w || height > h) {
// 缩放
- scaleWidth = ((float) width) / w;
- scaleHeight = ((float) height) / h;
+ if (w > 0) scaleWidth = ((float) width) / w;
+ if (h > 0) scaleHeight = ((float) height) / h;
}
options.inJustDecodeBounds = false;
int scale = (int) Math.ceil(Math.max(scaleWidth, scaleHeight));
@@ -189,6 +206,7 @@ public class BitmapUtils {
/**
* Drawable转Bitmap
+ *
* @param isSquare 是否是正方形
*/
public static Bitmap drawableToBitmap(Drawable drawable, boolean isSquare) {
@@ -196,7 +214,7 @@ public class BitmapUtils {
return null;
}
- int w,h;
+ int w, h;
// 取 drawable 的长宽
w = drawable.getIntrinsicWidth();
@@ -227,4 +245,108 @@ public class BitmapUtils {
return bitmap;
}
+ /**
+ * 修改bitmap透明度
+ *
+ * @param sourceImg
+ * @param config
+ * @param number
+ * @return
+ */
+ public static Bitmap getTransparentBitmap(Bitmap sourceImg, Bitmap.Config config, int number) {
+ int[] argb = new int[sourceImg.getWidth() * sourceImg.getHeight()];
+ sourceImg.getPixels(argb, 0, sourceImg.getWidth(), 0, 0, sourceImg
+ .getWidth(), sourceImg.getHeight());// 获得图片的ARGB值
+ number = number * 255 / 100;
+ for (int i = 0; i < argb.length; i++) {
+ argb[i] = (number << 24) | (argb[i] & 0x00FFFFFF);
+ }
+ sourceImg = Bitmap.createBitmap(argb, sourceImg.getWidth(), sourceImg
+ .getHeight(), config);
+ return sourceImg;
+ }
+
+
+ /**
+ * 高斯模糊
+ *
+ * @param context
+ * @param image
+ * @param radius
+ * @return
+ */
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static Bitmap getBlurBitmap(Context context, Bitmap image, @IntRange(from = 1, to = 25) int radius) {
+ // 计算图片缩小后的长宽
+ int width = Math.round(image.getWidth() * 0.8f);
+ int height = Math.round(image.getHeight() * 0.8f);
+
+ // 将缩小后的图片做为预渲染的图片。
+ Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
+ // 创建一张渲染后的输出图片。
+ Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
+ // 创建RenderScript内核对象
+ RenderScript rs = RenderScript.create(context);
+ // 创建一个模糊效果的RenderScript的工具对象
+ ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+
+ // 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间。
+ // 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去。
+ Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
+ Allocation tmpOut = Allocation.createTyped(rs, tmpIn.getType());
+ // 设置渲染的模糊程度, 25f是最大模糊度
+ blurScript.setRadius(radius);
+ // 设置blurScript对象的输入内存
+ blurScript.setInput(tmpIn);
+ // 将输出数据保存到输出内存中
+ blurScript.forEach(tmpOut);
+
+ // 将数据填充到Allocation中
+ tmpOut.copyTo(outputBitmap);
+
+ return outputBitmap;
+ }
+
+ /**
+ * 保存图片
+ *
+ * @param bitmap
+ * @param path
+ */
+ public static void saveBitmap(Bitmap bitmap, String path) {
+ File file = new File(path);
+ if (file.exists()) {
+ file.delete();
+ }
+ FileOutputStream out;
+ try {
+ out = new FileOutputStream(file);
+ if (bitmap.compress(Bitmap.CompressFormat.PNG, 80, out)) {
+ out.flush();
+ out.close();
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public static Bitmap compressBitmap(Bitmap bitmap) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ Matrix matrix = new Matrix();
+ Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ result.compress(Bitmap.CompressFormat.WEBP, 100, bos);
+
+ while (bos.toByteArray().length > 400 * 1024) {
+ System.out.println(bos.toByteArray().length);
+ matrix.setScale(0.9f, 0.9f);
+ result = Bitmap.createBitmap(result, 0, 0, result.getWidth(), result.getHeight(), matrix, true);
+ bos.reset();
+ result.compress(Bitmap.CompressFormat.WEBP, 100, bos);
+ }
+
+ return result;
+ }
}
diff --git a/app/src/main/java/com/gh/common/util/Extensions.kt b/app/src/main/java/com/gh/common/util/Extensions.kt
index 13ac076f8f..ce1b895f92 100644
--- a/app/src/main/java/com/gh/common/util/Extensions.kt
+++ b/app/src/main/java/com/gh/common/util/Extensions.kt
@@ -15,6 +15,7 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.PopupWindow
+import android.widget.SeekBar
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
@@ -268,6 +269,7 @@ fun Context.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
action()
}
}
+
/**
* 在限定 interval 里只触发一次 action
*/
@@ -425,7 +427,7 @@ fun Float.px2dip(): Int {
return (this / scale + 0.5f).toInt()
}
-fun Float.sp2px():Int{
+fun Float.sp2px(): Int {
val scale: Float = HaloApp.getInstance().application.resources.displayMetrics.scaledDensity
return (this * scale + 0.5f).toInt()
}
@@ -818,4 +820,21 @@ fun EditText.showKeyBoard() {
val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(this, 0)
}, 300)
+}
+
+
+fun SeekBar.doOnSeekBarChangeListener(progressChange: ((progress: Int) -> Unit)?=null, onStopTrackingTouch: (() -> Unit)? = null) {
+ this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+ progressChange?.invoke(progress)
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar?) {
+
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar?) {
+ onStopTrackingTouch?.invoke()
+ }
+ })
}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/common/util/UploadImageUtils.kt b/app/src/main/java/com/gh/common/util/UploadImageUtils.kt
index 7ad0a46793..6672758ea4 100644
--- a/app/src/main/java/com/gh/common/util/UploadImageUtils.kt
+++ b/app/src/main/java/com/gh/common/util/UploadImageUtils.kt
@@ -32,7 +32,8 @@ object UploadImageUtils {
suggestion,
icon,
poster,
- game_upload
+ game_upload,
+ user_background
}
// 不处理图片,只是单纯的上传
@@ -273,11 +274,11 @@ object UploadImageUtils {
}
// 防止GIF图片文件后缀不是GIF,这个FileName只是告诉服务端后缀格式,没有其他用处
- private fun getFileName(file: File): String {
+ fun getFileName(file: File): String {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.absolutePath, options)
- if (options.outMimeType.contains("gif") && !file.name.toLowerCase().contains(".gif".toLowerCase())) {
+ if (options.outMimeType != null && options.outMimeType.contains("gif") && !file.name.toLowerCase().contains(".gif".toLowerCase())) {
return System.currentTimeMillis().toString() + ".gif"
}
return URLEncoder.encode(file.name, "utf-8")
diff --git a/app/src/main/java/com/gh/common/view/AvatarBorderView.kt b/app/src/main/java/com/gh/common/view/AvatarBorderView.kt
new file mode 100644
index 0000000000..a170a59f46
--- /dev/null
+++ b/app/src/main/java/com/gh/common/view/AvatarBorderView.kt
@@ -0,0 +1,115 @@
+package com.gh.common.view
+
+import android.content.Context
+import android.graphics.drawable.ColorDrawable
+import android.util.AttributeSet
+import android.view.View
+import android.widget.RelativeLayout
+import androidx.core.content.ContextCompat
+import com.facebook.drawee.drawable.ScalingUtils
+import com.facebook.drawee.generic.GenericDraweeHierarchy
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
+import com.facebook.drawee.generic.RoundingParams
+import com.facebook.drawee.view.SimpleDraweeView
+import com.gh.common.util.ImageUtils
+import com.gh.common.util.dip2px
+import com.gh.gamecenter.R
+
+
+class AvatarBorderView : RelativeLayout {
+
+ var avatarView: SimpleDraweeView? = null
+ var borderView: SimpleDraweeView? = null
+ private var mAvatarWidth = 100f
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ initView(attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ initView(attrs)
+ }
+
+ private fun initView(attrs: AttributeSet) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.AvatarBorderView)
+ if (ta.hasValue(R.styleable.AvatarBorderView_avatar_width)) {
+ mAvatarWidth = ta.getDimension(R.styleable.AvatarBorderView_avatar_width, mAvatarWidth)
+ }
+ ta.recycle()
+
+ avatarView = createAvatarView()
+ addView(avatarView)
+ borderView = createBorderView()
+ addView(borderView)
+ requestLayout()
+ }
+
+ private fun createAvatarView(): SimpleDraweeView {
+ val avatarView = SimpleDraweeView(context)
+ avatarView.hierarchy = getGenericDraweeHierarchy()
+ val params = LayoutParams(mAvatarWidth.toInt(), mAvatarWidth.toInt())
+ params.addRule(CENTER_IN_PARENT)
+ avatarView.layoutParams = params
+ return avatarView
+ }
+
+ private fun createBorderView(): SimpleDraweeView {
+ val borderView = SimpleDraweeView(context)
+ borderView.hierarchy = getGenericBorderDraweeHierarchy()
+ val params = LayoutParams((mAvatarWidth * 3 / 2).toInt(), (mAvatarWidth * 3 / 2).toInt())
+ borderView.layoutParams = params
+ return borderView
+ }
+
+ private fun getGenericDraweeHierarchy(): GenericDraweeHierarchy {
+ val roundingParams = RoundingParams().apply {
+ roundAsCircle = true
+ borderWidth = 2f.dip2px().toFloat()
+ borderColor = ContextCompat.getColor(context, R.color.white)
+ }
+ return GenericDraweeHierarchyBuilder(resources)
+ .setFadeDuration(500)
+ .setRoundingParams(roundingParams)
+ .setPressedStateOverlay(ColorDrawable(ContextCompat.getColor(context, R.color.pressed_bg)))
+ .setPlaceholderImage(R.drawable.occupy2, ScalingUtils.ScaleType.CENTER)
+ .setBackground(ColorDrawable(ContextCompat.getColor(context, R.color.placeholder_bg)))
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .build()
+ }
+
+ private fun getGenericBorderDraweeHierarchy(): GenericDraweeHierarchy {
+ val roundingParams = RoundingParams()
+ return GenericDraweeHierarchyBuilder(resources)
+ .setFadeDuration(500)
+ .setRoundingParams(roundingParams)
+ .setPressedStateOverlay(ColorDrawable(ContextCompat.getColor(context, R.color.pressed_bg)))
+ .setPlaceholderImage(R.drawable.occupy2, ScalingUtils.ScaleType.CENTER)
+ .setBackground(ColorDrawable(ContextCompat.getColor(context, R.color.placeholder_bg)))
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .build()
+ }
+
+ fun display(borderUrl: String?, avatarUrl: String) {
+ if (!borderUrl.isNullOrEmpty()) {
+ borderView?.visibility = View.VISIBLE
+ ImageUtils.display(borderView, borderUrl)
+ } else {
+ borderView?.visibility = View.INVISIBLE
+ }
+ ImageUtils.display(avatarView, avatarUrl)
+ }
+
+ fun displayAvatar(avatarUrl: String) {
+ ImageUtils.display(avatarView, avatarUrl)
+ }
+
+ fun displayBorder(borderUrl: String?) {
+ if (!borderUrl.isNullOrEmpty()) {
+ borderView?.visibility = View.VISIBLE
+ ImageUtils.display(borderView, borderUrl)
+ } else {
+ borderView?.visibility = View.INVISIBLE
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/common/view/CropImageCustom.java b/app/src/main/java/com/gh/common/view/CropImageCustom.java
index bbdee76a48..6f71eef96b 100644
--- a/app/src/main/java/com/gh/common/view/CropImageCustom.java
+++ b/app/src/main/java/com/gh/common/view/CropImageCustom.java
@@ -1,12 +1,15 @@
package com.gh.common.view;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ImageView;
import android.widget.RelativeLayout;
+import com.gh.gamecenter.R;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -37,6 +40,9 @@ public class CropImageCustom extends RelativeLayout {
mHorizontalPadding = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources()
.getDisplayMetrics());
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropImageCustom);
+ mHorizontalPadding = (int) ta.getDimension(R.styleable.CropImageCustom_horizontalPadding, mHorizontalPadding);
+ ta.recycle();
mZoomImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
mClipImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
diff --git a/app/src/main/java/com/gh/common/view/CustomSeekBar.kt b/app/src/main/java/com/gh/common/view/CustomSeekBar.kt
new file mode 100644
index 0000000000..70f933584e
--- /dev/null
+++ b/app/src/main/java/com/gh/common/view/CustomSeekBar.kt
@@ -0,0 +1,94 @@
+package com.gh.common.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import androidx.appcompat.widget.AppCompatSeekBar
+import com.gh.common.util.DisplayUtils
+import com.gh.common.util.dip2px
+import com.gh.common.util.px2dip
+import com.gh.common.util.sp2px
+import com.gh.gamecenter.R
+
+/**
+ * 自定义滑块顶部带进度显示的SeekBar
+ */
+class CustomSeekBar : AppCompatSeekBar {
+
+ private var mProgressTextColor = Color.parseColor("#2496FF")
+ private var mProgressTextSize = 14f
+ private var mProgressTextPaddingBottom = 10f
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ initView(attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ initView(attrs)
+ }
+
+ private fun initView(attrs: AttributeSet) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBar)
+ mProgressTextColor = ta.getColor(R.styleable.CustomSeekBar_progressTextColor, mProgressTextColor)
+ if (ta.hasValue(R.styleable.CustomSeekBar_progressTextSize)) {
+ mProgressTextSize = ta.getDimension(R.styleable.CustomSeekBar_progressTextSize, mProgressTextSize)
+ mProgressTextSize = DisplayUtils.px2sp(context, mProgressTextSize).toFloat()
+ }
+ if (ta.hasValue(R.styleable.CustomSeekBar_progressTextPaddingBottom)) {
+ mProgressTextPaddingBottom = ta.getDimension(R.styleable.CustomSeekBar_progressTextPaddingBottom, mProgressTextPaddingBottom)
+ mProgressTextPaddingBottom = mProgressTextPaddingBottom.px2dip().toFloat()
+ }
+ ta.recycle()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val thumbHeight = if (thumb == null) 0 else thumb.intrinsicHeight
+ val measureText = measureText("0")
+ val dh = thumbHeight + measureText.second + mProgressTextPaddingBottom.dip2px()
+ setMeasuredDimension(
+ View.getDefaultSize(0, widthMeasureSpec),
+ View.resolveSizeAndState(dh, heightMeasureSpec, 0)
+ )
+ }
+
+ @SuppressLint("DrawAllocation")
+ override fun onDraw(canvas: Canvas?) {
+ canvas?.save()
+ //由于滑块和进度都是居中绘制,所以这里需要向下移动
+ canvas?.translate(0f, mProgressTextPaddingBottom.dip2px().toFloat())
+ super.onDraw(canvas)
+ canvas?.restore()
+
+ val paint = Paint()
+ paint.isAntiAlias = true
+ paint.textSize = mProgressTextSize.sp2px().toFloat()
+ paint.color = mProgressTextColor
+ val textString = "${progress * 100 / max}%"
+ val pair = measureText(textString)
+ val percent = progress.toFloat() / max
+ val progressWidth = width - paddingLeft - paddingRight
+ val offsetX = progressWidth * percent + paddingLeft - pair.first / 2
+ canvas?.drawText(
+ textString,
+ offsetX,
+ pair.second.toFloat() - paint.fontMetrics.descent,
+ paint
+ )
+ }
+
+ private fun measureText(str: String): Pair {
+ val paint = Paint()
+ paint.textSize = mProgressTextSize.sp2px().toFloat()
+ val rect = Rect()
+ paint.getTextBounds(str, 0, str.length, rect)
+ val w: Int = rect.width()
+ val h: Int = rect.height()
+ return Pair(w, h + paint.fontMetrics.descent.toInt())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java
index 5f48a4c873..09ca79c334 100644
--- a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java
+++ b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java
@@ -10,8 +10,6 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import androidx.annotation.NonNull;
-
import com.gh.base.ToolBarActivity;
import com.gh.common.util.BitmapUtils;
import com.gh.common.util.DisplayUtils;
@@ -21,6 +19,7 @@ import com.gh.common.view.CropImageCustom;
import java.io.File;
import java.lang.ref.SoftReference;
+import androidx.annotation.NonNull;
import butterknife.BindView;
public class CropImageActivity extends ToolBarActivity {
diff --git a/app/src/main/java/com/gh/gamecenter/entity/AvatarBorderEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/AvatarBorderEntity.kt
new file mode 100644
index 0000000000..63ea7ae65d
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/entity/AvatarBorderEntity.kt
@@ -0,0 +1,10 @@
+package com.gh.gamecenter.entity
+
+import com.google.gson.annotations.SerializedName
+
+data class AvatarBorderEntity(
+ @SerializedName("_id")
+ var id: String = "",
+ var url: String = "",
+ var name: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/entity/BackgroundImageEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/BackgroundImageEntity.kt
new file mode 100644
index 0000000000..ad20008921
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/entity/BackgroundImageEntity.kt
@@ -0,0 +1,15 @@
+package com.gh.gamecenter.entity
+
+import android.os.Parcelable
+import com.google.gson.annotations.SerializedName
+import kotlinx.android.parcel.Parcelize
+
+@Parcelize
+data class BackgroundImageEntity(
+ @SerializedName("_id")
+ var id: String = "",
+ var url: String = "",
+ var name: String = "",
+ var opacity: Int = 0,
+ var blur: Int = 1
+) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/entity/UserInfoEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/UserInfoEntity.kt
index e3120663f5..1d2ec0e924 100644
--- a/app/src/main/java/com/gh/gamecenter/entity/UserInfoEntity.kt
+++ b/app/src/main/java/com/gh/gamecenter/entity/UserInfoEntity.kt
@@ -38,4 +38,9 @@ class UserInfoEntity {
@SerializedName("login_mobile")
var loginMobile: String? = null
+
+ var background: BackgroundImageEntity? = null
+
+ @SerializedName("icon_border")
+ var iconBorder: AvatarBorderEntity? = null
}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowFragment.kt b/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowFragment.kt
index 1adb37e836..ef0538645e 100644
--- a/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowFragment.kt
+++ b/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowFragment.kt
@@ -54,6 +54,9 @@ class ForumMyFollowFragment : NormalFragment() {
mNoConnection.visibility = View.VISIBLE
}
})
+ mNoConnection.setOnClickListener {
+ mViewModel?.loadFollowsForum()
+ }
mRefresh.setOnRefreshListener {
mViewModel?.loadFollowsForum()
}
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundClipActivity.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundClipActivity.kt
new file mode 100644
index 0000000000..a43b1b6daa
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundClipActivity.kt
@@ -0,0 +1,85 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.widget.ImageView
+import com.gh.base.BaseActivity
+import com.gh.base.fragment.WaitingDialogFragment
+import com.gh.common.runOnIoThread
+import com.gh.common.util.BitmapUtils
+import com.gh.common.util.DisplayUtils
+import com.gh.common.util.EntranceUtils
+import com.gh.gamecenter.CropImageActivity
+import com.gh.gamecenter.R
+import com.gh.gamecenter.databinding.ActivityBackgroundClipBinding
+import java.io.File
+import java.lang.ref.SoftReference
+
+class BackgroundClipActivity : BaseActivity() {
+
+ private lateinit var mBinding: ActivityBackgroundClipBinding
+ private var reference: SoftReference? = null
+ private var mPostDialog: WaitingDialogFragment? = null
+
+ override fun getLayoutId(): Int = R.layout.activity_background_clip
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ DisplayUtils.transparentStatusBar(this)
+ mBinding = ActivityBackgroundClipBinding.bind(mContentView)
+ mBinding.cropImageIv.setCropRatio(2 / 3F)
+
+ mBinding.cancelTv.setOnClickListener {
+ finish()
+ }
+ mBinding.nextTv.setOnClickListener {
+ mPostDialog = WaitingDialogFragment.newInstance("正在生成预览...")
+ mPostDialog?.show(supportFragmentManager, null)
+ runOnIoThread {
+ val data = Intent()
+ val clipPath = cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".jpg"
+ mBinding.cropImageIv.savePicture(clipPath)
+
+ data.putExtra(CropImageActivity.RESULT_CLIP_PATH, clipPath)
+ setResult(Activity.RESULT_OK, data)
+ mPostDialog?.dismiss()
+ finish()
+ }
+
+ }
+ }
+
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ if (hasFocus && (reference == null || reference?.get() == null)) {
+ val imageView: ImageView = mBinding.cropImageIv.cropImageZoomView
+ val bitmap = BitmapUtils.getBitmapByFile(intent.getStringExtra(EntranceUtils.KEY_PATH),
+ imageView.width, imageView.height)
+ if (bitmap != null) {
+ reference = SoftReference(bitmap)
+ imageView.setImageBitmap(reference?.get())
+ }
+ }
+ }
+
+
+ override fun onDestroy() {
+ super.onDestroy()
+ if (reference != null && reference!!.get() != null) {
+ reference?.get()?.recycle()
+ }
+ }
+
+ companion object {
+ fun getIntent(context: Context, picturePath: String, entrance: String = ""): Intent? {
+ val intent = Intent(context, BackgroundClipActivity::class.java)
+ intent.putExtra(EntranceUtils.KEY_PATH, picturePath)
+ intent.putExtra(EntranceUtils.KEY_ENTRANCE, entrance)
+ return intent
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewActivity.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewActivity.kt
new file mode 100644
index 0000000000..d8cc181080
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewActivity.kt
@@ -0,0 +1,40 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.gh.base.BaseActivity
+import com.gh.common.util.DisplayUtils
+import com.gh.common.util.EntranceUtils
+import com.gh.gamecenter.NormalActivity
+import com.gh.gamecenter.R
+import com.gh.gamecenter.amway.AmwayFragment
+import com.gh.gamecenter.entity.BackgroundImageEntity
+
+class BackgroundPreviewActivity : BaseActivity() {
+
+ override fun getLayoutId(): Int = R.layout.activity_amway
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ DisplayUtils.transparentStatusBar(this)
+ DisplayUtils.setLightStatusBar(this, true)
+
+ val containerFragment = supportFragmentManager.findFragmentByTag(BackgroundPreviewFragment::class.java.simpleName)
+ ?: BackgroundPreviewFragment().with(intent.extras)
+ // 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
+ supportFragmentManager.beginTransaction().replace(R.id.placeholder, containerFragment, BackgroundPreviewFragment::class.java.simpleName).commitAllowingStateLoss()
+ }
+
+ companion object {
+ fun getIntent(context: Context, localPath: String, entity: BackgroundImageEntity?): Intent {
+ val intent = Intent(context, BackgroundPreviewActivity::class.java)
+ intent.putExtra(EntranceUtils.KEY_LOCAL_PATH, localPath)
+ if (entity != null) {
+ intent.putExtra(BackgroundImageEntity::class.java.simpleName, entity)
+ }
+ return intent
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewFragment.kt
new file mode 100644
index 0000000000..90dcb20890
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/BackgroundPreviewFragment.kt
@@ -0,0 +1,291 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.graphics.Bitmap
+import android.os.Build
+import android.os.Bundle
+import android.os.Looper
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.*
+import androidx.annotation.RequiresApi
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.databinding.DataBindingUtil
+import com.bytedance.sdk.open.aweme.utils.Md5Utils
+import com.gh.base.fragment.WaitingDialogFragment
+import com.gh.common.util.*
+import com.gh.gamecenter.CropImageActivity
+import com.gh.gamecenter.R
+import com.gh.gamecenter.databinding.FragmentBackgroundPreviewBinding
+import com.gh.gamecenter.entity.BackgroundImageEntity
+import com.gh.gamecenter.manager.UserManager
+import com.gh.gamecenter.normal.NormalFragment
+import com.gh.gamecenter.user.UserViewModel
+import com.halo.assistant.HaloApp
+import com.zhihu.matisse.Matisse
+import com.zhihu.matisse.MimeType
+import com.zhihu.matisse.engine.impl.PicassoEngine
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import java.io.File
+import kotlin.math.roundToInt
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener as OnGlobalFocusChangeListener
+
+class BackgroundPreviewFragment : NormalFragment() {
+
+ private var mOriginBitmap: Bitmap? = null
+ private var mTempBitmap: Bitmap? = null
+ private lateinit var mBinding: FragmentBackgroundPreviewBinding
+ private var mPostDialog: WaitingDialogFragment? = null
+ private var mLocalPath: String = ""
+ private var backgroundImageEntity: BackgroundImageEntity? = null//如果为空,则是本地选择的
+
+ private lateinit var mUserViewModel: UserViewModel
+
+ override fun getLayoutId(): Int = 0
+
+ override fun getInflatedLayout(): View {
+ mBinding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_background_preview, null, false)
+ return mBinding.root
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mUserViewModel = viewModelProvider(UserViewModel.Factory(HaloApp.getInstance().application))
+ mUserViewModel.uploadBackground.observeNonNull(this) {
+ mPostDialog?.dismiss()
+ if (it) {
+ requireActivity().finish()
+ mUserViewModel.uploadBackground.value = false
+ }
+ }
+
+ Looper.myQueue().addIdleHandler {
+ changeBackgroundViewParams()
+ false
+ }
+ }
+
+ private fun changeBackgroundViewParams() {
+ val screenHeight = DisplayUtils.px2dip(requireContext(), DisplayUtils.getScreenHeight().toFloat())
+ val picProportion = if (screenHeight > 640) 6 / 13f else 9 / 16f
+ val width = mBinding.previewMineIv.width
+ val height = mBinding.previewMineIv.height
+ val realHeight = width / picProportion
+
+ val mineGhIvParams = mBinding.personalHomeIv.layoutParams as ConstraintLayout.LayoutParams
+ mineGhIvParams.topMargin = ((height - realHeight) / 2).toInt()
+ mBinding.mineGhIv.layoutParams = mineGhIvParams
+
+ val personalHomeIvParams = mBinding.mineGhIv.layoutParams as ConstraintLayout.LayoutParams
+ personalHomeIvParams.topMargin = ((height - realHeight) / 2).toInt()
+ mBinding.personalHomeIv.layoutParams = personalHomeIvParams
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ mLocalPath = arguments?.getString(EntranceUtils.KEY_LOCAL_PATH) ?: ""
+ backgroundImageEntity = arguments?.getParcelable(BackgroundImageEntity::class.java.simpleName)
+ mOriginBitmap = BitmapUtils.getBitmapByFile(mLocalPath, Bitmap.Config.ARGB_8888)
+ mTempBitmap = Bitmap.createBitmap(mOriginBitmap)
+ mBinding.mineGhIv.setImageBitmap(mOriginBitmap)
+ mBinding.personalHomeIv.setImageBitmap(mOriginBitmap)
+
+ val screenHeight = DisplayUtils.px2dip(requireContext(), DisplayUtils.getScreenHeight().toFloat())
+ if (screenHeight > 640) {
+ mBinding.previewMineIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.preview_mine_full))
+ mBinding.previewHomeIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.preview_home_full))
+ } else {
+ mBinding.previewMineIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.preview_mine))
+ mBinding.previewHomeIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.preview_home))
+ }
+
+ mBinding.normalTitle.text = "预览"
+ mBinding.normalToolbar.setNavigationOnClickListener { requireActivity().finish() }
+
+ mBinding.alphaSeek.doOnSeekBarChangeListener({
+ mBinding.mineGhIv.alpha = it / 100f
+ mBinding.personalHomeIv.alpha = it / 100f
+ changeCommitButton()
+ })
+
+ mBinding.blurSeek.doOnSeekBarChangeListener({
+ changeCommitButton()
+ }, {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ changeAmbiguity()
+ } else {
+ ToastUtils.showToast("系统版本太低")
+ }
+ })
+
+ mBinding.changeBackgroundTv.setOnClickListener {
+ if (backgroundImageEntity == null) {
+ selectPic(requireActivity())
+ } else {
+ requireActivity().finish()
+ }
+ }
+
+ mBinding.commitTv.setOnClickListener {
+ PermissionHelper.checkStoragePermissionBeforeAction(requireContext(), object : EmptyCallback {
+ override fun onCallback() {
+ mTempBitmap?.let {
+ val filePath: String = "${requireContext().cacheDir.absolutePath}${File.separator}${Md5Utils.hexDigest(mLocalPath)}.webp"
+ savePicture(filePath, it)
+ }
+ }
+ })
+ }
+
+ val background = UserManager.getInstance().userInfoEntity.background
+ if (backgroundImageEntity != null && backgroundImageEntity!!.id == background?.id) {
+ mBinding.alphaSeek.progress = background.opacity
+ if (background.blur in 1..25 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ mBinding.blurSeek.progress = background.blur
+ changeAmbiguity()
+ }
+ mBinding.commitTv.text = "使用中"
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private fun changeAmbiguity() {
+ var progress = mBinding.blurSeek.progress
+ mTempBitmap = if (progress == 0) {
+ Bitmap.createBitmap(mOriginBitmap)
+ } else {
+ BitmapUtils.getBlurBitmap(requireContext(), mOriginBitmap, progress)
+ }
+ mBinding.mineGhIv.setImageBitmap(mTempBitmap)
+ mBinding.personalHomeIv.setImageBitmap(mTempBitmap)
+ }
+
+ private fun changeCommitButton() {
+ val background = UserManager.getInstance().userInfoEntity.background
+ if (backgroundImageEntity != null && backgroundImageEntity!!.id == background?.id) {
+ if (mBinding.alphaSeek.progress != background.opacity || mBinding.blurSeek.progress != background.blur) {
+ mBinding.commitTv.text = "使用"
+ mBinding.commitTv.isEnabled = true
+ } else {
+ mBinding.commitTv.isEnabled = false
+ mBinding.commitTv.text = "使用中"
+ }
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ private fun savePicture(path: String, bitmap: Bitmap) {
+ mPostDialog = WaitingDialogFragment.newInstance("加载中...")
+ mPostDialog?.show(childFragmentManager, null)
+ Single.just(bitmap)
+ .map {
+ if (mBinding.alphaSeek.progress == 100) {
+ it
+ } else {
+ BitmapUtils.getTransparentBitmap(bitmap, Bitmap.Config.ARGB_8888, mBinding.alphaSeek.progress)
+ }
+ }
+ .map {
+ BitmapUtils.compressBitmap(it)
+ }
+ .map {
+ BitmapUtils.saveBitmap(it, path)
+ path
+ }
+ .flatMap {
+ uploadImage(it)
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ mPostDialog?.dismiss()
+ val entity = BackgroundImageEntity(backgroundImageEntity?.id
+ ?: "", it, opacity = mBinding.alphaSeek.progress, blur = mBinding.blurSeek.progress)
+ mUserViewModel.changeUserInfo(GsonUtils.toJson(entity), UserViewModel.TYPE_BACKGROUND)
+ }, {
+ it.printStackTrace()
+ mPostDialog?.dismiss()
+ })
+
+ }
+
+
+ private fun uploadImage(path: String): Single {
+ return Single.create {
+ UploadImageUtils.uploadImage(UploadImageUtils.UploadType.user_background, path, object : UploadImageUtils.OnUploadImageListener {
+ override fun onSuccess(imageUrl: String) {
+ it.onSuccess(imageUrl)
+ }
+
+ override fun onError(e: Throwable?) {
+ it.onError(e ?: Throwable())
+ }
+
+ override fun onProgress(total: Long, progress: Long) {
+
+ }
+
+ })
+ }
+ }
+
+
+ private fun selectPic(activity: Activity) {
+ PermissionHelper.checkStoragePermissionBeforeAction(activity, object : EmptyCallback {
+ override fun onCallback() {
+ Matisse.from(activity)
+ .choose(MimeType.ofImage())
+ .showSingleMediaType(true)
+ .countable(true)
+ .addFilter(GhMatisseFilter())
+ .maxSelectable(1)
+ .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .thumbnailScale(0.85f)
+ .imageEngine(PicassoEngine())
+ .forResult(MEDIA_STORE_REQUEST)
+ }
+ })
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == MEDIA_STORE_REQUEST && resultCode == Activity.RESULT_OK) {
+ val selectedPaths = Matisse.obtainPathResult(data)
+ if (selectedPaths.size > 0) {
+ val intent = BackgroundClipActivity.getIntent(requireContext(), selectedPaths[0], mEntrance)
+ startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
+ }
+ } else if (requestCode == REQUEST_CODE_IMAGE_CROP && resultCode == Activity.RESULT_OK) {
+ if (data != null) {
+ val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH)
+ mOriginBitmap = BitmapUtils.getBitmapByFile(imagePath, Bitmap.Config.ARGB_8888)
+ val progress = mBinding.blurSeek.progress
+ mTempBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && progress != 0) {
+ BitmapUtils.getBlurBitmap(requireContext(), mOriginBitmap, progress)
+ } else {
+ Bitmap.createBitmap(mOriginBitmap)
+ }
+ mBinding.mineGhIv.setImageBitmap(mTempBitmap)
+ mBinding.personalHomeIv.setImageBitmap(mTempBitmap)
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ mOriginBitmap = null
+ mTempBitmap = null
+ }
+
+ companion object {
+ const val REQUEST_CODE_IMAGE_CROP = 100
+ const val MEDIA_STORE_REQUEST = 101
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundActivity.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundActivity.kt
new file mode 100644
index 0000000000..556b57f661
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundActivity.kt
@@ -0,0 +1,20 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.gh.gamecenter.NormalActivity
+
+class PersonalityBackgroundActivity : NormalActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setNavigationTitle("个性背景")
+ }
+
+ companion object {
+ fun getIntent(context: Context): Intent {
+ return getTargetIntent(context, PersonalityBackgroundActivity::class.java, PersonalityBackgroundFragment::class.java)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundAdapter.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundAdapter.kt
new file mode 100644
index 0000000000..2a38f4e0ec
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundAdapter.kt
@@ -0,0 +1,45 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.gh.base.BaseRecyclerViewHolder
+import com.gh.common.util.ImageUtils
+import com.gh.common.util.visibleIf
+import com.gh.gamecenter.R
+import com.gh.gamecenter.databinding.AvatarBackgroundItemBinding
+import com.gh.gamecenter.entity.BackgroundImageEntity
+import com.gh.gamecenter.manager.UserManager
+import com.lightgame.adapter.BaseRecyclerAdapter
+import java.util.ArrayList
+
+class PersonalityBackgroundAdapter(context: Context,val mViewModel: PersonalityBackgroundViewModel) : BaseRecyclerAdapter(context) {
+
+ var mEntityList: ArrayList = arrayListOf()
+
+ fun setListData(datas: ArrayList) {
+ mEntityList.addAll(datas)
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return PendantBackgroundViewHolder(AvatarBackgroundItemBinding.bind(mLayoutInflater.inflate(R.layout.avatar_background_item, parent, false)))
+ }
+
+ override fun getItemCount(): Int = mEntityList.size
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ if (holder is PendantBackgroundViewHolder) {
+ val entity = mEntityList[position]
+ ImageUtils.display(holder.binding.imageSdv, entity.url)
+ holder.binding.nameTv.text = entity.name
+ holder.binding.checkBorderView.visibleIf(UserManager.getInstance().userInfoEntity.background?.id == entity.id)
+ holder.binding.checkIv.visibleIf(UserManager.getInstance().userInfoEntity.background?.id == entity.id)
+ holder.itemView.setOnClickListener {
+ mViewModel.downLoadImage(mContext,entity)
+ }
+ }
+ }
+
+ class PendantBackgroundViewHolder(val binding: AvatarBackgroundItemBinding) : BaseRecyclerViewHolder(binding.root)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundFragment.kt
new file mode 100644
index 0000000000..c16e5ba9d3
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundFragment.kt
@@ -0,0 +1,114 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.gh.base.fragment.WaitingDialogFragment
+import com.gh.common.util.*
+import com.gh.common.view.GridSpacingItemColorDecoration
+import com.gh.common.view.GridSpacingItemDecoration
+import com.gh.gamecenter.CropImageActivity
+import com.gh.gamecenter.R
+import com.gh.gamecenter.normal.NormalFragment
+import com.gh.gamecenter.user.UserViewModel
+import com.halo.assistant.HaloApp
+import com.zhihu.matisse.Matisse
+import com.zhihu.matisse.MimeType
+import com.zhihu.matisse.engine.impl.PicassoEngine
+import kotterknife.bindView
+
+class PersonalityBackgroundFragment : NormalFragment() {
+ private val mUploadBackgroundLl by bindView(R.id.uploadBackgroundLl)
+ private val mRecommendRv by bindView(R.id.recommendRv)
+ private var mAdapter: PersonalityBackgroundAdapter? = null
+ private lateinit var mViewModel: PersonalityBackgroundViewModel
+ private lateinit var mUserViewModel: UserViewModel
+
+ private var mLoadingDialog: WaitingDialogFragment? = null
+
+ override fun getLayoutId(): Int = R.layout.personality_background_fragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mUserViewModel = viewModelProvider(UserViewModel.Factory(HaloApp.getInstance().application))
+ mViewModel = viewModelProvider()
+ mUserViewModel.editObsUserinfo.observe(this, Observer {
+ mAdapter?.notifyDataSetChanged()
+ })
+ mViewModel.backgroundImagesLiveData.observe(this, Observer {
+ mAdapter?.setListData(it)
+ })
+ mViewModel.loadingLiveData.observe(this, Observer {
+ if (it) {
+ mLoadingDialog = WaitingDialogFragment.newInstance("下载图片中...")
+ mLoadingDialog?.show(childFragmentManager, null)
+ } else {
+ mLoadingDialog?.dismiss()
+ }
+ })
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ mRecommendRv.apply {
+ layoutManager = GridLayoutManager(requireContext(), 3)
+ mAdapter = PersonalityBackgroundAdapter(requireContext(), mViewModel)
+ addItemDecoration(GridSpacingItemColorDecoration(requireContext(), 8, 20, R.color.white))
+ adapter = mAdapter
+ }
+ mUploadBackgroundLl.setOnClickListener {
+ selectPic(requireActivity())
+ }
+
+ }
+
+ private fun selectPic(activity: Activity) {
+ PermissionHelper.checkStoragePermissionBeforeAction(activity, object : EmptyCallback {
+ override fun onCallback() {
+ Matisse.from(activity)
+ .choose(MimeType.ofImage())
+ .showSingleMediaType(true)
+ .countable(true)
+ .addFilter(GhMatisseFilter())
+ .maxSelectable(1)
+ .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .thumbnailScale(0.85f)
+ .imageEngine(PicassoEngine())
+ .forResult(MEDIA_STORE_REQUEST)
+ }
+ })
+ }
+
+ @SuppressLint("Recycle")
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == MEDIA_STORE_REQUEST && resultCode == Activity.RESULT_OK) {
+ val selectedPaths = Matisse.obtainPathResult(data)
+ if (selectedPaths.size > 0) {
+ val intent = BackgroundClipActivity.getIntent(requireContext(), selectedPaths[0], mEntrance)
+ startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
+ }
+ } else if (requestCode == REQUEST_CODE_IMAGE_CROP && resultCode == Activity.RESULT_OK) {
+ if (data != null) {
+ val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH)
+ val intent = BackgroundPreviewActivity.getIntent(requireContext(), imagePath, null)
+ requireContext().startActivity(intent)
+ }
+ }
+ }
+
+ companion object {
+ const val REQUEST_CODE_IMAGE_CROP = 100
+ const val MEDIA_STORE_REQUEST = 101
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundViewModel.kt b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundViewModel.kt
new file mode 100644
index 0000000000..4a41141f44
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/background/PersonalityBackgroundViewModel.kt
@@ -0,0 +1,68 @@
+package com.gh.gamecenter.personalhome.background
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import android.net.Uri
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import com.bytedance.sdk.open.aweme.utils.Md5Utils
+import com.gh.common.util.BitmapUtils
+import com.gh.gamecenter.entity.BackgroundImageEntity
+import com.gh.gamecenter.retrofit.Response
+import com.gh.gamecenter.retrofit.RetrofitManager
+import com.squareup.picasso.Picasso
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import java.io.File
+
+class PersonalityBackgroundViewModel(application: Application) : AndroidViewModel(application) {
+
+ val loadingLiveData= MutableLiveData()
+ val backgroundImagesLiveData = MutableLiveData>()
+
+ init {
+ getBackgroundImages()
+ }
+
+ fun getBackgroundImages() {
+ RetrofitManager.getInstance(getApplication()).api
+ .backgroundImages
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : Response>() {
+ override fun onResponse(response: ArrayList?) {
+ super.onResponse(response)
+ if (!response.isNullOrEmpty()) {
+ backgroundImagesLiveData.postValue(response)
+ }
+ }
+ })
+ }
+
+ @SuppressLint("CheckResult")
+ fun downLoadImage(context: Context, entity: BackgroundImageEntity) {
+ loadingLiveData.postValue(true)
+ Single.just(entity.url)
+ .map {
+ Picasso.with(getApplication()).load(Uri.parse(it)).priority(Picasso.Priority.HIGH).get()
+ }
+ .map {
+ val fileName = entity.url.substring(entity.url.lastIndexOf("/") + 1)
+ val filePath: String = "${context.cacheDir.absolutePath}${File.separator}$fileName"
+ BitmapUtils.saveBitmap(it, filePath)
+ filePath
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ loadingLiveData.postValue(false)
+ val intent = BackgroundPreviewActivity.getIntent(context, it, entity)
+ context.startActivity(intent)
+ }, {
+ it.printStackTrace()
+ loadingLiveData.postValue(false)
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderActivity.kt b/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderActivity.kt
new file mode 100644
index 0000000000..9da71e87d0
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderActivity.kt
@@ -0,0 +1,33 @@
+package com.gh.gamecenter.personalhome.border
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.gh.base.BaseActivity
+import com.gh.common.util.DisplayUtils
+import com.gh.gamecenter.R
+
+class AvatarBorderActivity : BaseActivity() {
+
+ override fun getLayoutId(): Int {
+ return R.layout.activity_amway
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ DisplayUtils.transparentStatusBar(this)
+
+ val containerFragment = supportFragmentManager.findFragmentByTag(AvatarBorderFragment::class.java.simpleName)
+ ?: AvatarBorderFragment().with(intent.extras)
+ // 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
+ supportFragmentManager.beginTransaction().replace(R.id.placeholder, containerFragment, AvatarBorderFragment::class.java.simpleName).commitAllowingStateLoss()
+ }
+
+ companion object {
+ fun getIntent(context: Context): Intent {
+ return Intent(context, AvatarBorderActivity::class.java)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderFragment.kt
new file mode 100644
index 0000000000..c90ab49dff
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/border/AvatarBorderFragment.kt
@@ -0,0 +1,127 @@
+package com.gh.gamecenter.personalhome.border
+
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import com.gh.base.fragment.BaseFragment_TabLayout
+import com.gh.base.fragment.WaitingDialogFragment
+import com.gh.common.util.*
+import com.gh.gamecenter.ImageViewerActivity
+import com.gh.gamecenter.R
+import com.gh.gamecenter.SelectUserIconActivity
+import com.gh.gamecenter.databinding.FragmentAvatarBorderBinding
+import com.gh.gamecenter.entity.AvatarBorderEntity
+import com.gh.gamecenter.manager.UserManager
+import com.gh.gamecenter.user.UserViewModel
+import com.halo.assistant.HaloApp
+
+class AvatarBorderFragment : BaseFragment_TabLayout() {
+
+ private lateinit var mUserViewModel: UserViewModel
+
+ lateinit var mBinding: FragmentAvatarBorderBinding
+
+ private var mPostDialog: WaitingDialogFragment? = null
+
+ var selectAvatarBorderEntity: AvatarBorderEntity? = null
+
+
+ override fun getInflatedLayout(): View {
+ mBinding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_avatar_border, null, false)
+ return mBinding.root
+ }
+
+ override fun initFragmentList(fragments: MutableList) {
+ fragments.add(ChooseAvatarBorderFragment())
+ }
+
+ override fun initTabTitleList(tabTitleList: MutableList) {
+ tabTitleList.add("推荐边框")
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mUserViewModel = viewModelProvider(UserViewModel.Factory(HaloApp.getInstance().application))
+ mUserViewModel.loginObsUserinfo.observe(this, Observer {
+ ImageUtils.display(mBinding.forumBackground, it.data.background?.url)
+ mBinding.userAvatar.display(it.data.iconBorder?.url, it.data.icon ?: "")
+ })
+ mUserViewModel.editObsUserinfo.observe(this, Observer {
+ mBinding.userAvatar.displayAvatar(it.data.icon ?: "")
+ })
+ mUserViewModel.uploadAvatarBorder.observe(this, Observer {
+ mPostDialog?.dismiss()
+ if (it) {
+ requireActivity().finish()
+ mUserViewModel.uploadAvatarBorder.value = false
+ }
+ })
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ ViewCompat.setOnApplyWindowInsetsListener(mBinding.appbar) { _, insets ->
+ (mBinding.toolbar.layoutParams as ViewGroup.MarginLayoutParams).topMargin = insets.systemWindowInsetTop
+ insets.consumeSystemWindowInsets()
+ }
+
+ mBinding.toolbar.setNavigationOnClickListener { requireActivity().finish() }
+
+ mBinding.collapsingToolbar.scrimShownAction = {
+ DisplayUtils.setLightStatusBar(requireActivity(), it)
+ if (it) {
+ mBinding.titleTv.alpha = 1F
+ mBinding.titleTv.setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
+ mBinding.toolbar.navigationIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_bar_back)
+ } else {
+ mBinding.titleTv.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
+ mBinding.toolbar.navigationIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_toolbar_back_white)
+ }
+ }
+
+ mBinding.userAvatar.setOnClickListener {
+ startActivity(ImageViewerActivity.getIntent(requireContext(), arrayListOf(UserManager.getInstance().userInfoEntity.icon
+ ?: ""), 0, mBinding.userAvatar, "$mEntrance+(头像挂件)"))
+ }
+
+ mBinding.changeAvatarTv.setOnClickListener {
+ startActivity(SelectUserIconActivity.getIntent(context))
+ }
+
+ mBinding.commitTv.setOnClickListener {
+ if (selectAvatarBorderEntity != null) {
+ mPostDialog = WaitingDialogFragment.newInstance("加载中...")
+ mPostDialog?.show(childFragmentManager, null)
+
+ if (mBinding.commitTv.text == "停用挂件") {
+ mUserViewModel.changeUserInfo(GsonUtils.toJson(AvatarBorderEntity("", "", "")), UserViewModel.TYPE_ICON_BORDER)
+ } else {
+ mUserViewModel.changeUserInfo(GsonUtils.toJson(selectAvatarBorderEntity), UserViewModel.TYPE_ICON_BORDER)
+ }
+ }
+ }
+ }
+
+ fun choosePendant(entity: AvatarBorderEntity, isSelected: Boolean) {
+ if (isSelected) {
+ selectAvatarBorderEntity = entity
+ mBinding.userAvatar.displayBorder(entity.url)
+ mBinding.commitTv.isEnabled = true
+ if (entity.id == UserManager.getInstance().userInfoEntity.iconBorder?.id) {
+ mBinding.commitTv.text = "停用挂件"
+ } else {
+ mBinding.commitTv.text = "使用"
+ }
+ } else {
+ selectAvatarBorderEntity = null
+ mBinding.userAvatar.displayBorder("")
+ mBinding.commitTv.text = "使用"
+ mBinding.commitTv.isEnabled = false
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderAdapter.kt b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderAdapter.kt
new file mode 100644
index 0000000000..7ff23fd55e
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderAdapter.kt
@@ -0,0 +1,66 @@
+package com.gh.gamecenter.personalhome.border
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.gh.base.BaseRecyclerViewHolder
+import com.gh.common.util.ImageUtils
+import com.gh.common.util.goneIf
+import com.gh.gamecenter.R
+import com.gh.gamecenter.databinding.AvatarItemBinding
+import com.gh.gamecenter.entity.AvatarBorderEntity
+import com.gh.gamecenter.manager.UserManager
+import com.lightgame.adapter.BaseRecyclerAdapter
+import java.util.*
+
+class ChooseAvatarBorderAdapter(context: Context, val clickCallback: (entity: AvatarBorderEntity, isSelected: Boolean) -> Unit) : BaseRecyclerAdapter(context) {
+
+ var entityList: ArrayList = arrayListOf()
+ var selectedEntity: AvatarBorderEntity? = null
+
+ fun setListData(updateData: List) {
+ entityList.clear()
+ entityList.addAll(updateData)
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return PendantBackgroundViewHolder(AvatarItemBinding.bind(mLayoutInflater.inflate(R.layout.avatar_item, parent, false)))
+ }
+
+ override fun getItemCount(): Int = entityList.size
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ if (holder is PendantBackgroundViewHolder) {
+ val borderEntity = entityList[position]
+ ImageUtils.display(holder.binding.imageSdv, borderEntity.url)
+ holder.binding.nameTv.text = borderEntity.name
+ val iconBorder = UserManager.getInstance().userInfoEntity.iconBorder
+ if (iconBorder?.id != borderEntity.id) {
+ holder.binding.checkIv.visibility = View.GONE
+ } else {
+ holder.binding.checkIv.visibility = View.VISIBLE
+ clickCallback.invoke(borderEntity, true)
+ }
+ holder.itemView.setOnClickListener {
+ if (selectedEntity == null) {
+ selectedEntity = borderEntity
+ clickCallback.invoke(borderEntity, true)
+ } else {
+ selectedEntity = if (selectedEntity?.id == borderEntity.id) {
+ clickCallback.invoke(borderEntity, false)
+ null
+ } else {
+ clickCallback.invoke(borderEntity, true)
+ borderEntity
+ }
+ }
+ holder.binding.checkIv.goneIf(selectedEntity?.id != borderEntity.id)
+ holder.binding.checkBorderView.goneIf(selectedEntity?.id != borderEntity.id)
+ }
+ }
+ }
+
+ class PendantBackgroundViewHolder(val binding: AvatarItemBinding) : BaseRecyclerViewHolder(binding.root)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderFragment.kt
new file mode 100644
index 0000000000..08e157fe0a
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderFragment.kt
@@ -0,0 +1,76 @@
+package com.gh.gamecenter.personalhome.border
+
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import com.gh.common.util.dip2px
+import com.gh.common.util.viewModelProvider
+import com.gh.common.view.GridSpacingItemColorDecoration
+import com.gh.gamecenter.R
+import com.gh.gamecenter.normal.NormalFragment
+import kotterknife.bindView
+
+class ChooseAvatarBorderFragment : NormalFragment() {
+
+ val mRefresh by bindView(R.id.list_refresh)
+ val mListRv by bindView(R.id.list_rv)
+ val mReuseLoading by bindView(R.id.reuse_ll_loading)
+ val mNoConnection by bindView(R.id.reuse_no_connection)
+ val mNoneData by bindView(R.id.reuse_none_data)
+
+ private var mAdapter: ChooseAvatarBorderAdapter? = null
+ private var mViewModel: ChooseAvatarBorderViewModel? = null
+
+ override fun getLayoutId(): Int = R.layout.fragment_list_base
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mViewModel = viewModelProvider()
+ mViewModel?.pendantsLiveData?.observe(this, Observer {
+ mReuseLoading.visibility = View.GONE
+ if (it != null) {
+ mNoConnection.visibility = View.GONE
+ if (it.isNotEmpty()) {
+ mNoneData.visibility = View.GONE
+ mAdapter?.setListData(it)
+ } else {
+ mNoneData.visibility = View.VISIBLE
+ }
+ } else {
+ mNoneData.visibility = View.GONE
+ mNoConnection.visibility = View.VISIBLE
+ }
+ })
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ mRefresh.isEnabled = false
+ val params = mRefresh.layoutParams as RelativeLayout.LayoutParams
+ params.leftMargin=16f.dip2px()
+ params.rightMargin=16f.dip2px()
+ params.topMargin=10f.dip2px()
+ mRefresh.layoutParams=params
+ mListRv.apply {
+ layoutManager = GridLayoutManager(requireContext(), 3)
+ mAdapter = ChooseAvatarBorderAdapter(requireContext()) { entity, isSelected ->
+ val avatarPendantFragment = parentFragment as? AvatarBorderFragment
+ avatarPendantFragment?.choosePendant(entity,isSelected)
+ }
+ addItemDecoration(GridSpacingItemColorDecoration(requireContext(), 8, 8, R.color.white))
+ adapter = mAdapter
+ }
+
+ mNoConnection.setOnClickListener {
+ mViewModel?.getPendants()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderViewModel.kt b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderViewModel.kt
new file mode 100644
index 0000000000..ad32837491
--- /dev/null
+++ b/app/src/main/java/com/gh/gamecenter/personalhome/border/ChooseAvatarBorderViewModel.kt
@@ -0,0 +1,37 @@
+package com.gh.gamecenter.personalhome.border
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import com.gh.gamecenter.entity.AvatarBorderEntity
+import com.gh.gamecenter.retrofit.Response
+import com.gh.gamecenter.retrofit.RetrofitManager
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import retrofit2.HttpException
+
+class ChooseAvatarBorderViewModel(application: Application) : AndroidViewModel(application) {
+ val pendantsLiveData = MutableLiveData>()
+
+ init {
+ getPendants()
+ }
+
+ fun getPendants() {
+ RetrofitManager.getInstance(getApplication()).api
+ .pendants
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : Response>() {
+ override fun onResponse(response: ArrayList?) {
+ super.onResponse(response)
+ pendantsLiveData.postValue(response)
+ }
+
+ override fun onFailure(e: HttpException?) {
+ super.onFailure(e)
+ pendantsLiveData.postValue(null)
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
index 05212eabaa..e6b2650467 100644
--- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
+++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
@@ -8,6 +8,8 @@ import com.gh.gamecenter.entity.AmwayCommentEntity;
import com.gh.gamecenter.entity.ApkEntity;
import com.gh.gamecenter.entity.AppEntity;
import com.gh.gamecenter.entity.AuthDialogEntity;
+import com.gh.gamecenter.entity.AvatarBorderEntity;
+import com.gh.gamecenter.entity.BackgroundImageEntity;
import com.gh.gamecenter.entity.BadgeEntity;
import com.gh.gamecenter.entity.CategoryEntity;
import com.gh.gamecenter.entity.CommentEntity;
@@ -2629,5 +2631,18 @@ public interface ApiService {
* 取消点赞游戏视频的评论
*/
@POST("videos/{video_id}/comments/{comment_id}:unvote")
- Observable unVoteVideoComment(@Path("video_id") String videoId,@Path("comment_id") String commentId);
+ Observable unVoteVideoComment(@Path("video_id") String videoId, @Path("comment_id") String commentId);
+
+ /**
+ * 获取个人主页的默认背景图列表
+ */
+ @GET("users/background_images")
+ Observable> getBackgroundImages();
+
+ /**
+ * 获取用户头像默认边框列表
+ */
+ @GET("users/icon_borders")
+ Observable> getPendants();
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/user/UserRepository.java b/app/src/main/java/com/gh/gamecenter/user/UserRepository.java
index 7b6f42ef7e..54648bd6e4 100644
--- a/app/src/main/java/com/gh/gamecenter/user/UserRepository.java
+++ b/app/src/main/java/com/gh/gamecenter/user/UserRepository.java
@@ -6,9 +6,6 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.text.TextUtils;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MediatorLiveData;
-
import com.gh.common.PushManager;
import com.gh.common.constant.Constants;
import com.gh.common.im.ImManager;
@@ -22,8 +19,10 @@ import com.gh.common.util.LoginUtils;
import com.gh.common.util.SPUtils;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.R;
+import com.gh.gamecenter.entity.BackgroundImageEntity;
import com.gh.gamecenter.entity.IdCardEntity;
import com.gh.gamecenter.entity.LoginTokenEntity;
+import com.gh.gamecenter.entity.AvatarBorderEntity;
import com.gh.gamecenter.entity.TokenEntity;
import com.gh.gamecenter.entity.UserInfoEntity;
import com.gh.gamecenter.eventbus.EBReuse;
@@ -44,6 +43,8 @@ import org.json.JSONObject;
import java.util.HashMap;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@@ -66,6 +67,8 @@ public class UserRepository {
private final MediatorLiveData> mLoginObsResponseUserInfo = new MediatorLiveData<>();
private final MediatorLiveData> mEditObsResponseUserInfo = new MediatorLiveData<>();
+ private final MediatorLiveData mUploadBackgroundLiveData = new MediatorLiveData<>();
+ private final MediatorLiveData mUploadAvatarBorderLiveData = new MediatorLiveData<>();
private final MediatorLiveData mObservableLoginToken = new MediatorLiveData<>();
@@ -286,7 +289,9 @@ public class UserRepository {
}
JSONObject object = new JSONObject();
try {
- if (editType.equals(UserViewModel.TYPE_ID_CARD)) {
+ if (editType.equals(UserViewModel.TYPE_ID_CARD) ||
+ editType.equals(UserViewModel.TYPE_BACKGROUND) ||
+ editType.equals(UserViewModel.TYPE_ICON_BORDER)) {
object = new JSONObject();
object.put(editType, new JSONObject(content));
} else {
@@ -332,6 +337,15 @@ public class UserRepository {
break;
case UserViewModel.TYPE_INTRODUCE:
mCacheUserInfoEntity.setIntroduce(content);
+ break;
+ case UserViewModel.TYPE_BACKGROUND:
+ mCacheUserInfoEntity.setBackground(GsonUtils.fromJson(content, BackgroundImageEntity.class));
+ mUploadBackgroundLiveData.postValue(true);
+ break;
+ case UserViewModel.TYPE_ICON_BORDER:
+ mCacheUserInfoEntity.setIconBorder(GsonUtils.fromJson(content, AvatarBorderEntity.class));
+ mUploadAvatarBorderLiveData.postValue(true);
+ break;
}
userInfoHandle(mCacheUserInfoEntity, true);
@@ -346,6 +360,12 @@ public class UserRepository {
value.setThrowable(e.getThrowable());
value.setHttpException(httpException);
mEditObsResponseUserInfo.postValue(value);
+ if (editType.equals(UserViewModel.TYPE_BACKGROUND)) {
+ mUploadBackgroundLiveData.postValue(false);
+ }
+ if (editType.equals(UserViewModel.TYPE_ICON_BORDER)) {
+ mUploadAvatarBorderLiveData.postValue(false);
+ }
if (httpException == null || httpException.code() == Constants.NOT_NETWORK_CODE) {
Utils.toast(mContext, "请检查网络是否可用");
@@ -488,4 +508,11 @@ public class UserRepository {
public MediatorLiveData> getEditObsUserInfo() {
return mEditObsResponseUserInfo;
}
+
+ public MediatorLiveData getUploadBackgroundLiveData() {
+ return mUploadBackgroundLiveData;
+ }
+ public MediatorLiveData getUploadAvatarBorderLiveData() {
+ return mUploadAvatarBorderLiveData;
+ }
}
diff --git a/app/src/main/java/com/gh/gamecenter/user/UserViewModel.java b/app/src/main/java/com/gh/gamecenter/user/UserViewModel.java
index 902f578584..67c5d1335c 100644
--- a/app/src/main/java/com/gh/gamecenter/user/UserViewModel.java
+++ b/app/src/main/java/com/gh/gamecenter/user/UserViewModel.java
@@ -2,16 +2,17 @@ package com.gh.gamecenter.user;
import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-
import com.gh.gamecenter.entity.UserInfoEntity;
import org.json.JSONObject;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
/**
* Created by khy on 28/11/17.
*/
@@ -32,17 +33,23 @@ public class UserViewModel extends AndroidViewModel {
public static final String TYPE_REGION = "region";
public static final String TYPE_ID_CARD = "id_card";
public static final String TYPE_INTRODUCE = "introduce";
+ public static final String TYPE_BACKGROUND = "background";
+ public static final String TYPE_ICON_BORDER = "icon_border";
private UserRepository mUserRepository;
private final LiveData> mLoginLiveUserInfo;
private final LiveData> mEditLiveUserInfo;
+ private final MediatorLiveData mUploadBackground;
+ private final MediatorLiveData mUploadAvatarBorder;
public UserViewModel(@NonNull Application application, UserRepository repository) {
super(application);
mUserRepository = repository;
mLoginLiveUserInfo = repository.getLoginUserInfo();
mEditLiveUserInfo = repository.getEditObsUserInfo();
+ mUploadBackground = repository.getUploadBackgroundLiveData();
+ mUploadAvatarBorder = repository.getUploadAvatarBorderLiveData();
}
public void retryCheckLogin() {
@@ -57,6 +64,14 @@ public class UserViewModel extends AndroidViewModel {
return mEditLiveUserInfo;
}
+ public MediatorLiveData getUploadBackground() {
+ return mUploadBackground;
+ }
+
+ public MediatorLiveData getUploadAvatarBorder() {
+ return mUploadAvatarBorder;
+ }
+
public void login(JSONObject content, LoginTag loginTag) {
mUserRepository.login(content, loginTag);
}
diff --git a/app/src/main/res/drawable-xxhdpi/ic_avatar_border_select.png b/app/src/main/res/drawable-xxhdpi/ic_avatar_border_select.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c9cca48d44d32c86751a10911c9c588d30daf99
GIT binary patch
literal 1873
zcmV-X2d?;uP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91QJ@0=1ONa40RR91P5=M^0BM%4?*IS>yGcYrRCodHoNb5|RT#(rGrQYn
zwwh^{5^GjwD2DhU+{{`n)U=GO1gj0iYIQ9!)F3LP50zwMV5Jrzp?=V!=nXBjL@g^Q
z0x2vDL(9x8Q!`z6z0?1hyL0#4**kY;@66mg_s$%+JLf#-%z63k^PKa{nZszKUolP?
z!1;)I(13r*;h?7BXU)KewKaHm+k@J#Ua)9{c$zoYdMO;IGK6`S<6TpA4i^cF~X?u2J&UPa1>6wkM8JDWX
zbR#imm4|Db+E~@vhTGMGx;UAtAc?yZpyEwYONpO&Ocf+?cLLPLE7VdtA941nf+X%v
zfYzI}LW?>7;C@?DLM1@!&Dv|wQ#A=h6;L5@UGI&(bRr2i3562ScTLw$abDbCXa&iA
zfIP8?8Yc6i4{b0ZCX0&?g1ps)$T{&Ff7QhMZWCg)j!)u^!Qbdjh&!9L{qknvc|
zWMDvDcwMYktLBr-W;))(R9QL3?;H*+91C!YGQ*fb+;-^&G;IXhQ^AiYW6n~wUX8WE
zH&NUgr`Z5ntq<-44DWBHg1u%z{QM#9TmJWU2irvPY7>Bq6b&p^%UKYmQJQpFYNMF&
zV>fh~upQvOqri8E?b2Y^P$w=1!BkH
zoh_0XcDS-HFn4tNC-Fjp1<+bV>~#phmiK^P{;}r-m(E(9Gu7%e=@cep(ji0|>Vc(`
zfa0cinrYPpyIr4KnFN=Bbvc!`(L
zW|G~9q?NA04P?{+$_N}GZn{r9uIVPot|JXVvTP?$4V=zhkrT)$0+bOrLX^y&Jxr56
zDe<7g0iL^<%T?1WNC7C1a!#SxCam_Orj&vyie%aFLROQt~8CH#sWi0Msc)17O9qCVhxohOatD*(aK0a57JDRoM
zsSMBBZwxZ+2GXgmQfOeVzPLh^n=TxL=c$b}s_;U0K}H3j5b*?Y(`2ocljkGhAxzio
z6=akEdWI+kcg@j0;?DP5>EVX=*9~OU0163DnrI>GQH@8bk)fZgbpjbhfWpSr#jyZd
z*M?Qb!c+=Nt#p{zi+KvL`C4FTKj6nBz^bo+KSKW(dK;1I;&dD0-5ZUn14poxEB1}v
znybuGD#UJ?v*37hx^}jz7Wa^TsJlGngudGlWpJ`_OP8mKlK~Yn2?aqEZ02fhU`c|o
zo2#=OSbaY{MQz++m&Yw#uqFy9nIBof44QUG@6pQR-{A|Q43TF28+TeJ7sG#yDpKDN
zWnf(Hzvkr*n)aEf4m8Yu1p-kz_(hAhg=T#}qhEO=K#D_7Z5&mJ&)P+UC?Mq#vA!?HG5TC3EjUz;
z(u9VU#za1Iucet^RGJ?6Yt`4zw!fx3@0R^Y?vRh9MsDwvmS?P}=4?kKJLEL)@w$#h
z<-B^A%$)5&<aXUG*Nale?_0$%O0Wy
zQ?Bxj*HkO+=cfzCQ$t5_Q5s6OWGFSZp6P--lvqNvLzQ&HMx#gS
z7>4L=1e%6Am_&P1bL)tjSsP_-e9vAdbx8Ohl^Ef+>oUee00000
LNkvXXu0mjfa|>(2
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable-xxhdpi/ic_background_add.png b/app/src/main/res/drawable-xxhdpi/ic_background_add.png
new file mode 100644
index 0000000000000000000000000000000000000000..3994b92f0e98d9b99205c427f126bbb403fb02ab
GIT binary patch
literal 513
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
P00000NkvXXu0mjfz4g%S
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable-xxxhdpi/bg_avatar_border.webp b/app/src/main/res/drawable-xxxhdpi/bg_avatar_border.webp
new file mode 100644
index 0000000000000000000000000000000000000000..babc645b865833a242aab2749a20a6b48a674068
GIT binary patch
literal 2418
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
F`~!&4ovHu;
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable-xxxhdpi/preview_home.png b/app/src/main/res/drawable-xxxhdpi/preview_home.png
new file mode 100644
index 0000000000000000000000000000000000000000..81b5c8e1b012963a78358fcc19972df8fc2daf10
GIT binary patch
literal 140456
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℞iVw2}F_!z0SW%RKu?qP&V
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