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 0000000000..4c9cca48d4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_avatar_border_select.png differ 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 0000000000..3994b92f0e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_background_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_seekbar_thumb.png b/app/src/main/res/drawable-xxhdpi/ic_seekbar_thumb.png new file mode 100644 index 0000000000..b6d6bbe35b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_seekbar_thumb.png differ 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 0000000000..babc645b86 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_avatar_border.webp differ 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 0000000000..81b5c8e1b0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/preview_home.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/preview_home_full.png b/app/src/main/res/drawable-xxxhdpi/preview_home_full.png new file mode 100644 index 0000000000..a624378a5e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/preview_home_full.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/preview_mine.png b/app/src/main/res/drawable-xxxhdpi/preview_mine.png new file mode 100644 index 0000000000..243c58f4b3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/preview_mine.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/preview_mine_full.png b/app/src/main/res/drawable-xxxhdpi/preview_mine_full.png new file mode 100644 index 0000000000..f00a07dae3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/preview_mine_full.png differ diff --git a/app/src/main/res/drawable/background_shape_white_radius_4.xml b/app/src/main/res/drawable/background_shape_white_radius_4.xml new file mode 100644 index 0000000000..02abad050c --- /dev/null +++ b/app/src/main/res/drawable/background_shape_white_radius_4.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_background_preview_top.xml b/app/src/main/res/drawable/bg_background_preview_top.xml new file mode 100644 index 0000000000..1f9d557a6d --- /dev/null +++ b/app/src/main/res/drawable/bg_background_preview_top.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_border_blue_radius_4_width_1.xml b/app/src/main/res/drawable/bg_border_blue_radius_4_width_1.xml new file mode 100644 index 0000000000..54e5c4e614 --- /dev/null +++ b/app/src/main/res/drawable/bg_border_blue_radius_4_width_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_shape_fa_radius_4.xml b/app/src/main/res/drawable/bg_shape_fa_radius_4.xml new file mode 100644 index 0000000000..0a9ed71f8f --- /dev/null +++ b/app/src/main/res/drawable/bg_shape_fa_radius_4.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/border_round_ccc_999.xml b/app/src/main/res/drawable/border_round_ccc_999.xml new file mode 100644 index 0000000000..4eca779200 --- /dev/null +++ b/app/src/main/res/drawable/border_round_ccc_999.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/seekbar_progress.xml b/app/src/main/res/drawable/seekbar_progress.xml new file mode 100644 index 0000000000..79623cf620 --- /dev/null +++ b/app/src/main/res/drawable/seekbar_progress.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_background_clip.xml b/app/src/main/res/layout/activity_background_clip.xml new file mode 100644 index 0000000000..53838c319b --- /dev/null +++ b/app/src/main/res/layout/activity_background_clip.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/avatar_background_item.xml b/app/src/main/res/layout/avatar_background_item.xml new file mode 100644 index 0000000000..633af49120 --- /dev/null +++ b/app/src/main/res/layout/avatar_background_item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/avatar_item.xml b/app/src/main/res/layout/avatar_item.xml new file mode 100644 index 0000000000..3c792afa86 --- /dev/null +++ b/app/src/main/res/layout/avatar_item.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_avatar_border.xml b/app/src/main/res/layout/fragment_avatar_border.xml new file mode 100644 index 0000000000..4757631faa --- /dev/null +++ b/app/src/main/res/layout/fragment_avatar_border.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_background_preview.xml b/app/src/main/res/layout/fragment_background_preview.xml new file mode 100644 index 0000000000..310c0798ba --- /dev/null +++ b/app/src/main/res/layout/fragment_background_preview.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/personality_background_fragment.xml b/app/src/main/res/layout/personality_background_fragment.xml new file mode 100644 index 0000000000..8395f14745 --- /dev/null +++ b/app/src/main/res/layout/personality_background_fragment.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7d09d1e1e2..ef792e93fe 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,36 +1,36 @@ - + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - + + + - - - + + + @@ -74,54 +74,68 @@ - + - + - + - + - + - + - + - + - + - + - - + + - - - + + + - - - - - + + + + + - - - + + + - + - + - + - \ No newline at end of file + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2b91ee7d3f..b528ab6fe7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -82,6 +82,7 @@ @android:color/black #1A000000 #4D000000 + #66000000 #80000000 #99FFFFFF @@ -195,5 +196,6 @@ #140B6D #16161A #28282E + #3796FF \ No newline at end of file