diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index dc233f9e..13c80f98 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,3 @@ # These are supported funding model platforms -custom: https://github.com/xuexiangjys/Resource/blob/master/doc/sponsor.md +custom: https://gitee.com/xuexiangjys/Resource/blob/master/doc/sponsor.md diff --git a/README.md b/README.md index 83b6f6e7..a0f94fc9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ ## X系列库快速集成 -为了方便大家快速集成X系列框架库,我提供了一个空壳模版供大家参考使用: https://github.com/xuexiangjys/TemplateAppProject +为了方便大家快速集成X系列框架库,我提供了一个空壳模版供大家参考使用: [https://github.com/xuexiangjys/TemplateAppProject](https://github.com/xuexiangjys/TemplateAppProject) + +除此之外,我还特别制作了几期[视频教程](https://space.bilibili.com/483850585/channel/detail?cid=104998)供大家学习参考. ---- @@ -63,12 +65,12 @@ allprojects { dependencies { ... //androidx项目 - implementation 'com.github.xuexiangjys:XUI:1.1.2' + implementation 'com.github.xuexiangjys:XUI:1.1.3' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'com.google.android.material:material:1.1.0-beta01' - implementation 'com.github.bumptech.glide:glide:4.8.0' + implementation 'com.github.bumptech.glide:glide:4.11.0' } ``` @@ -223,6 +225,10 @@ C*y | 1¥ | 微信 *清红 | 1¥ | 支付宝 *口 | 5¥ | 微信 \* | 10.24¥ | 微信 +*俊耀 | 100¥ | 支付宝 +*俊杰 | 1¥ | 支付宝 +*鸥 | 10.24¥ | 微信 +*云 | 20.21¥ | 支付宝 ## 联系方式 diff --git a/app/build.gradle b/app/build.gradle index be283311..de12d9e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId "com.xuexiang.xuidemo" minSdkVersion 17 targetSdkVersion build_versions.target_sdk - versionCode 13 - versionName "1.1.2" + versionCode 14 + versionName "1.1.3" multiDexEnabled true vectorDrawables.useSupportLibrary = true @@ -99,16 +99,16 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'com.google.android.material:material:1.1.0-beta01' - implementation 'com.github.bumptech.glide:glide:4.8.0' + implementation 'com.github.bumptech.glide:glide:4.11.0' //XUI框架 -// implementation project(':xui_lib') - implementation 'com.github.xuexiangjys:XUI:1.1.2' + implementation project(':xui_lib') +// implementation 'com.github.xuexiangjys:XUI:1.1.2' // implementation 'com.qmuiteam:qmui:1.2.0' //工具类 - implementation 'com.github.xuexiangjys.XUtil:xutil-core:1.1.6' - implementation 'com.github.xuexiangjys.XUtil:xutil-sub:1.1.6' + implementation 'com.github.xuexiangjys.XUtil:xutil-core:1.1.7' + implementation 'com.github.xuexiangjys.XUtil:xutil-sub:1.1.7' //切片 implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.0.5x' //页面路由 @@ -191,8 +191,11 @@ dependencies { //相机拍摄 implementation 'com.github.xuexiangjys:CameraView:1.0.3' + //另一款相机拍摄库,使用更简单 + implementation 'com.wonderkiln:camerakit:0.13.1' + //版本更新 - implementation 'com.github.xuexiangjys:XUpdate:1.1.1' + implementation 'com.github.xuexiangjys:XUpdate:1.1.4' implementation 'com.zhy:okhttputils:2.6.2' //腾讯的键值对存储mmkv implementation 'com.tencent:mmkv:1.0.22' @@ -204,6 +207,9 @@ dependencies { implementation 'com.umeng.umsdk:analytics:8.0.2' implementation 'com.umeng.umsdk:common:2.0.2' + //ANR异常捕获 + implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0' + //bugly统计 implementation 'com.tencent.bugly:crashreport:3.0.0' //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e4a57a04..4d68375f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -180,6 +180,16 @@ -keep class com.google.gson.stream.** { *; } -keep class com.google.gson.examples.android.model.** { *; } +# Glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +# for DexGuard only +-keepresourcexmlelements manifest/application/meta-data@value=GlideModule + # Retrofit -dontwarn retrofit2.** -keep class retrofit2.** { *; } @@ -300,3 +310,15 @@ # 数据库 -keep class com.xuexiang.xuidemo.base.db.entity.** { *; } + +# PictureSelector +-keep class com.luck.picture.lib.** { *; } +-dontwarn com.yalantis.ucrop** +-keep class com.yalantis.ucrop** { *; } +-keep interface com.yalantis.ucrop** { *; } + +# camerakit +-dontwarn com.google.android.gms.** +-keepclasseswithmembers class com.camerakit.preview.CameraSurfaceView { + native ; +} diff --git a/app/src/main/java/com/xuexiang/xuidemo/MyApp.java b/app/src/main/java/com/xuexiang/xuidemo/MyApp.java index 3a5dc154..cfd07c89 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/MyApp.java +++ b/app/src/main/java/com/xuexiang/xuidemo/MyApp.java @@ -11,6 +11,7 @@ import com.xuexiang.xormlite.annotation.DataBase; import com.xuexiang.xormlite.enums.DataBaseType; import com.xuexiang.xui.XUI; +import com.xuexiang.xuidemo.utils.sdkinit.ANRWatchDogInit; import com.xuexiang.xuidemo.utils.sdkinit.AutoCameraStrategy; import com.xuexiang.xuidemo.utils.sdkinit.BuglyInit; import com.xuexiang.xuidemo.utils.sdkinit.TbsInit; @@ -50,6 +51,8 @@ public void onCreate() { UMengInit.init(this); BuglyInit.init(this); } + //ANR监控 + ANRWatchDogInit.init(); } /** diff --git a/app/src/main/java/com/xuexiang/xuidemo/fragment/AboutFragment.java b/app/src/main/java/com/xuexiang/xuidemo/fragment/AboutFragment.java index 2ec3e9b2..8d613e5d 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/fragment/AboutFragment.java +++ b/app/src/main/java/com/xuexiang/xuidemo/fragment/AboutFragment.java @@ -43,7 +43,8 @@ public class AboutFragment extends BaseFragment { TextView mVersionTextView; @BindView(R.id.about_list) XUIGroupListView mAboutGroupListView; - @BindView(R.id.copyright) TextView mCopyrightTextView; + @BindView(R.id.copyright) + TextView mCopyrightTextView; @Override protected int getLayoutId() { @@ -78,6 +79,11 @@ public void onClick(View v) { public void onClick(View v) { Utils.checkUpdate(getContext(), true); } + }).addItemView(mAboutGroupListView.createItemView(getResources().getString(R.string.about_item_sponsor)), new View.OnClickListener() { + @Override + public void onClick(View v) { + openPage(SponsorFragment.class); + } }) .addItemView(mAboutGroupListView.createItemView(getResources().getString(R.string.about_item_add_qq_group)), v -> Utils.goWeb(getContext(), getString(R.string.url_add_qq_group))) .addTo(mAboutGroupListView); diff --git a/app/src/main/java/com/xuexiang/xuidemo/fragment/LoginFragment.java b/app/src/main/java/com/xuexiang/xuidemo/fragment/LoginFragment.java index bb93afe8..fe0e3aaf 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/fragment/LoginFragment.java +++ b/app/src/main/java/com/xuexiang/xuidemo/fragment/LoginFragment.java @@ -31,6 +31,8 @@ import com.xuexiang.xuidemo.R; import com.xuexiang.xuidemo.activity.MainActivity; import com.xuexiang.xuidemo.base.BaseFragment; +import com.xuexiang.xuidemo.utils.PrivacyUtils; +import com.xuexiang.xuidemo.utils.SettingSPUtils; import com.xuexiang.xuidemo.utils.TokenUtils; import com.xuexiang.xuidemo.utils.XToastUtils; import com.xuexiang.xutil.app.ActivityUtils; @@ -77,6 +79,14 @@ protected TitleBar initTitle() { protected void initViews() { mCountDownHelper = new CountDownButtonHelper(btnGetVerifyCode, 60); + //隐私政策弹窗 + SettingSPUtils spUtils = SettingSPUtils.getInstance(); + if (!spUtils.isAgreePrivacy()) { + PrivacyUtils.showPrivacyDialog(getContext(), (dialog, which) -> { + dialog.dismiss(); + spUtils.setIsAgreePrivacy(true); + }); + } } @SingleClick diff --git a/app/src/main/java/com/xuexiang/xuidemo/fragment/SponsorFragment.java b/app/src/main/java/com/xuexiang/xuidemo/fragment/SponsorFragment.java new file mode 100644 index 00000000..c9a5f23f --- /dev/null +++ b/app/src/main/java/com/xuexiang/xuidemo/fragment/SponsorFragment.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xuexiang.xuidemo.fragment; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageView; + +import com.xuexiang.xaop.annotation.SingleClick; +import com.xuexiang.xpage.annotation.Page; +import com.xuexiang.xqrcode.XQRCode; +import com.xuexiang.xqrcode.util.QRCodeAnalyzeUtils; +import com.xuexiang.xui.widget.dialog.bottomsheet.BottomSheet; +import com.xuexiang.xuidemo.R; +import com.xuexiang.xuidemo.base.BaseFragment; +import com.xuexiang.xuidemo.fragment.components.imageview.DrawablePreviewFragment; +import com.xuexiang.xuidemo.utils.XToastUtils; +import com.xuexiang.xutil.app.PathUtils; +import com.xuexiang.xutil.app.SocialShareUtils; +import com.xuexiang.xutil.display.ImageUtils; +import com.xuexiang.xutil.file.FileUtils; +import com.xuexiang.xutil.net.NetworkUtils; + +import java.io.File; + +import butterknife.BindView; + +import static com.xuexiang.xuidemo.fragment.components.imageview.DrawablePreviewFragment.DRAWABLE_ID; + +/** + * 赞助页面 + * + * @author xuexiang + * @since 2020-02-19 13:49 + */ +@Page(name = "赞助项目") +public class SponsorFragment extends BaseFragment implements View.OnClickListener, View.OnLongClickListener { + + @BindView(R.id.iv_wei_xin_pay) + AppCompatImageView ivWeiXinPay; + @BindView(R.id.iv_ali_pay) + AppCompatImageView ivAliPay; + + @Override + protected int getLayoutId() { + return R.layout.fragment_sponsor; + } + + @Override + protected void initViews() { + + } + + @Override + protected void initListeners() { + ivAliPay.setTag("ali_pay"); + ivAliPay.setOnClickListener(this); + ivAliPay.setOnLongClickListener(this); + ivWeiXinPay.setTag("wei_xin_pay"); + ivWeiXinPay.setOnClickListener(this); + ivWeiXinPay.setOnLongClickListener(this); + } + + @SingleClick + @Override + public void onClick(View v) { + Bundle bundle = new Bundle(); + switch (v.getId()) { + case R.id.iv_wei_xin_pay: + bundle.putInt(DRAWABLE_ID, R.drawable.img_wei_xin_pay); + openPage(DrawablePreviewFragment.class, bundle); + break; + case R.id.iv_ali_pay: + bundle.putInt(DRAWABLE_ID, R.drawable.img_ali_pay); + openPage(DrawablePreviewFragment.class, bundle); + break; + default: + break; + } + } + + @Override + public boolean onLongClick(View v) { + if (v instanceof ImageView) { + showBottomSheetList(v, ImageUtils.drawable2Bitmap(((ImageView) v).getDrawable())); + } + return true; + } + + @NonNull + private String getImgFilePath(View v) { + return FileUtils.getDiskFilesDir() + File.separator + v.getTag() + ".png"; + } + + private void showBottomSheetList(View v, final Bitmap bitmap) { + final String imgPath = getImgFilePath(v); + BottomSheet.BottomListSheetBuilder builder = new BottomSheet.BottomListSheetBuilder(getActivity()) + .addItem("发送给朋友") + .addItem("保存图片"); + if (v.getId() == R.id.iv_ali_pay) { + builder.addItem("识别图中的二维码"); + } + builder.setOnSheetItemClickListener((dialog, itemView, position, tag) -> { + dialog.dismiss(); + boolean result = checkFile(imgPath, bitmap); + switch (position) { + case 0: + if (result) { + SocialShareUtils.sharePicture(getActivity(), PathUtils.getUriForFile(FileUtils.getFileByPath(imgPath))); + } else { + XToastUtils.toast("图片发送失败!"); + } + break; + case 1: + if (result) { + XToastUtils.toast("图片保存成功:" + imgPath); + } else { + XToastUtils.toast("图片保存失败!"); + } + break; + case 2: + if (result) { + XQRCode.analyzeQRCode(imgPath, new QRCodeAnalyzeUtils.AnalyzeCallback() { + @Override + public void onAnalyzeSuccess(Bitmap mBitmap, String result) { + if (NetworkUtils.isUrlValid(result)) { + goWeb(result); + } + } + + @Override + public void onAnalyzeFailed() { + XToastUtils.toast("解析二维码失败!"); + } + }); + } else { + XToastUtils.toast("二维码识别失败!"); + } + break; + default: + break; + } + }) + .build() + .show(); + } + + private boolean checkFile(String imgPath, Bitmap bitmap) { + boolean result = FileUtils.isFileExists(imgPath); + if (!result) { + result = ImageUtils.save(bitmap, imgPath, Bitmap.CompressFormat.PNG); + } + return result; + } + + /** + * 以系统API的方式请求浏览器 + * + * @param url + */ + public void goWeb(final String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(intent); + } + +} diff --git a/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/CameraFragment.java b/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/CameraFragment.java index 1c77d46f..1634482e 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/CameraFragment.java +++ b/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/CameraFragment.java @@ -29,9 +29,8 @@ import com.xuexiang.xuidemo.R; import com.xuexiang.xuidemo.base.BaseFragment; import com.xuexiang.xuidemo.fragment.expands.camera.CameraActivity; +import com.xuexiang.xuidemo.fragment.expands.camera.CameraKitFragment; import com.xuexiang.xuidemo.fragment.expands.camera.CameraViewActivity; -import com.xuexiang.xutil.app.PathUtils; -import com.xuexiang.xutil.file.FileUtils; import butterknife.BindView; import butterknife.OnClick; @@ -61,7 +60,7 @@ protected void initViews() { } @SingleClick - @OnClick({R.id.btn_camera, R.id.btn_camera_complex}) + @OnClick({R.id.btn_camera, R.id.btn_camera_complex, R.id.btn_camera_kit}) public void onViewClicked(View view) { switch(view.getId()) { case R.id.btn_camera: @@ -70,6 +69,9 @@ public void onViewClicked(View view) { case R.id.btn_camera_complex: CameraViewActivity.open(this); break; + case R.id.btn_camera_kit: + openNewPage(CameraKitFragment.class); + break; default: break; } diff --git a/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/camera/CameraKitFragment.java b/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/camera/CameraKitFragment.java new file mode 100644 index 00000000..aeb36686 --- /dev/null +++ b/app/src/main/java/com/xuexiang/xuidemo/fragment/expands/camera/CameraKitFragment.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xuexiang.xuidemo.fragment.expands.camera; + +import android.graphics.Bitmap; +import android.view.View; + +import androidx.appcompat.widget.AppCompatImageView; + +import com.wonderkiln.camerakit.CameraKit; +import com.wonderkiln.camerakit.CameraKitError; +import com.wonderkiln.camerakit.CameraKitEvent; +import com.wonderkiln.camerakit.CameraKitEventListener; +import com.wonderkiln.camerakit.CameraKitImage; +import com.wonderkiln.camerakit.CameraKitVideo; +import com.wonderkiln.camerakit.CameraView; +import com.xuexiang.xaop.annotation.MainThread; +import com.xuexiang.xaop.annotation.SingleClick; +import com.xuexiang.xpage.annotation.Page; +import com.xuexiang.xui.widget.actionbar.TitleBar; +import com.xuexiang.xui.widget.alpha.XUIAlphaImageView; +import com.xuexiang.xuidemo.R; +import com.xuexiang.xuidemo.base.BaseFragment; +import com.xuexiang.xuidemo.utils.Utils; + +import butterknife.BindView; +import butterknife.OnClick; + +import static com.wonderkiln.camerakit.CameraKit.Constants.FLASH_AUTO; +import static com.wonderkiln.camerakit.CameraKit.Constants.FLASH_OFF; +import static com.wonderkiln.camerakit.CameraKit.Constants.FLASH_ON; +import static com.wonderkiln.camerakit.CameraKit.Constants.FLASH_TORCH; + +/** + * CameraKit 相机工具 + * + * @author xuexiang + * @since 2020-02-13 10:47 + */ +@Page(name = "CameraKit") +public class CameraKitFragment extends BaseFragment { + + @BindView(R.id.camera_view) + CameraView cameraView; + @BindView(R.id.iv_photo) + AppCompatImageView ivPhoto; + @BindView(R.id.iv_flash_light) + XUIAlphaImageView ivFlashLight; + + @Override + protected int getLayoutId() { + return R.layout.fragment_camerakit; + } + + @Override + protected TitleBar initTitle() { + TitleBar titleBar = super.initTitle(); + titleBar.addAction(new TitleBar.TextAction("Github") { + @Override + public void performAction(View view) { + Utils.goWeb(getContext(), "https://github.com/CameraKit/camerakit-android"); + } + }); + return titleBar; + } + + @Override + protected void initViews() { + switchFlashIcon(cameraView.getFlash()); + cameraView.addCameraKitListener(new CameraKitEventListener() { + @Override + public void onEvent(CameraKitEvent cameraKitEvent) { + + } + + @Override + public void onError(CameraKitError cameraKitError) { + + } + + @MainThread + @Override + public void onImage(CameraKitImage cameraKitImage) { + Bitmap bitmap = cameraKitImage.getBitmap(); + ivPhoto.setImageBitmap(bitmap); + } + + @Override + public void onVideo(CameraKitVideo cameraKitVideo) { + + } + }); + } + + @SingleClick + @OnClick({R.id.iv_flash_light, R.id.iv_face, R.id.iv_take_photo}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.iv_flash_light: + switchFlashIcon(cameraView.toggleFlash()); + break; + case R.id.iv_face: + cameraView.toggleFacing(); + break; + case R.id.iv_take_photo: + cameraView.captureImage(); + break; + default: + break; + } + } + + private void switchFlashIcon(int flash) { + switch (flash) { + case FLASH_OFF: + ivFlashLight.setImageResource(R.drawable.ic_flash_off); + break; + case FLASH_ON: + ivFlashLight.setImageResource(R.drawable.ic_flash_on); + break; + case FLASH_AUTO: + ivFlashLight.setImageResource(R.drawable.ic_flash_auto); + break; + default: + break; + } + } + + @Override + public void onResume() { + super.onResume(); + cameraView.start(); + } + + @Override + public void onPause() { + cameraView.stop(); + super.onPause(); + } +} diff --git a/app/src/main/java/com/xuexiang/xuidemo/utils/PrivacyUtils.java b/app/src/main/java/com/xuexiang/xuidemo/utils/PrivacyUtils.java new file mode 100644 index 00000000..06d86835 --- /dev/null +++ b/app/src/main/java/com/xuexiang/xuidemo/utils/PrivacyUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xuexiang.xuidemo.utils; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.xuexiang.xui.utils.ResUtils; +import com.xuexiang.xui.widget.dialog.DialogLoader; +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; +import com.xuexiang.xuidemo.R; +import com.xuexiang.xutil.XUtil; + +/** + * 隐私政策工具类 + * + * @author xuexiang + * @since 2020-02-23 23:33 + */ +public final class PrivacyUtils { + + private PrivacyUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * 这里填写你的应用隐私政策网页地址 + */ + private static final String PRIVACY_URL = "https://gitee.com/xuexiangjys/XUI/raw/master/LICENSE"; + + /** + * 显示隐私政策的提示 + * + * @param context + * @param submitListener 同意的监听 + * @return + */ + public static Dialog showPrivacyDialog(Context context, MaterialDialog.SingleButtonCallback submitListener) { + MaterialDialog dialog = new MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false).cancelable(false) + .positiveText(R.string.lab_agree).onPositive((dialog1, which) -> { + if (submitListener != null) { + submitListener.onClick(dialog1, which); + } else { + dialog1.dismiss(); + } + }) + .negativeText(R.string.lab_disagree).onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + DialogLoader.getInstance().showConfirmDialog(context, ResUtils.getString(R.string.title_reminder), String.format(ResUtils.getString(R.string.content_privacy_explain_again), ResUtils.getString(R.string.app_name)), ResUtils.getString(R.string.lab_look_again), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + showPrivacyDialog(context, submitListener); + } + }, ResUtils.getString(R.string.lab_still_disagree), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + DialogLoader.getInstance().showConfirmDialog(context, ResUtils.getString(R.string.content_think_about_it_again), ResUtils.getString(R.string.lab_look_again), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + showPrivacyDialog(context, submitListener); + } + }, ResUtils.getString(R.string.lab_exit_app), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + XUtil.get().exitApp(); + } + }); + } + }); + } + }).build(); + dialog.setContent(getPrivacyContent(context)); + //开始响应点击事件 + dialog.getContentView().setMovementMethod(LinkMovementMethod.getInstance()); + dialog.show(); + return dialog; + } + + /** + * @return 隐私政策说明 + */ + private static SpannableStringBuilder getPrivacyContent(Context context) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder() + .append(" 欢迎来到").append(ResUtils.getString(R.string.app_name)).append("!\n") + .append(" 我们深知个人信息对你的重要性,也感谢你对我们的信任。\n") + .append(" 为了更好地保护你的权益,同时遵守相关监管的要求,我们将通过"); + stringBuilder.append(getPrivacyLink(context, PRIVACY_URL)) + .append("向你说明我们会如何收集、存储、保护、使用及对外提供你的信息,并说明你享有的权利。\n") + .append(" 更多详情,敬请查阅") + .append(getPrivacyLink(context, PRIVACY_URL)) + .append("全文。"); + return stringBuilder; + } + + /** + * @param context 隐私政策的链接 + * @return + */ + private static SpannableString getPrivacyLink(Context context, String privacyUrl) { + String privacyName = String.format(ResUtils.getString(R.string.lab_privacy_name), ResUtils.getString(R.string.app_name)); + SpannableString spannableString = new SpannableString(privacyName); + spannableString.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + Utils.goWeb(context, privacyUrl); + } + }, 0, privacyName.length(), Spanned.SPAN_MARK_MARK); + return spannableString; + } + + +} diff --git a/app/src/main/java/com/xuexiang/xuidemo/utils/SettingSPUtils.java b/app/src/main/java/com/xuexiang/xuidemo/utils/SettingSPUtils.java index 1093c08e..a685d3aa 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/utils/SettingSPUtils.java +++ b/app/src/main/java/com/xuexiang/xuidemo/utils/SettingSPUtils.java @@ -21,6 +21,7 @@ private SettingSPUtils(Context context) { /** * 获取单例 + * * @return */ public static SettingSPUtils getInstance() { @@ -37,6 +38,8 @@ public static SettingSPUtils getInstance() { private final String IS_FIRST_OPEN_KEY = "is_first_open_key"; + private final String IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key"; + private final String IS_USE_CUSTOM_THEME_KEY = "is_use_custom_theme_key"; @@ -54,6 +57,17 @@ public void setIsFirstOpen(boolean isFirstOpen) { putBoolean(IS_FIRST_OPEN_KEY, isFirstOpen); } + /** + * @return 是否同意隐私政策 + */ + public boolean isAgreePrivacy() { + return getBoolean(IS_AGREE_PRIVACY_KEY, false); + } + + public void setIsAgreePrivacy(boolean isAgreePrivacy) { + putBoolean(IS_AGREE_PRIVACY_KEY, isAgreePrivacy); + } + public boolean isUseCustomTheme() { return getBoolean(IS_USE_CUSTOM_THEME_KEY, false); } diff --git a/app/src/main/java/com/xuexiang/xuidemo/utils/TokenUtils.java b/app/src/main/java/com/xuexiang/xuidemo/utils/TokenUtils.java index eff55e31..dcf2ca9a 100644 --- a/app/src/main/java/com/xuexiang/xuidemo/utils/TokenUtils.java +++ b/app/src/main/java/com/xuexiang/xuidemo/utils/TokenUtils.java @@ -59,6 +59,10 @@ public static void clearToken() { MMKV.defaultMMKV().remove(KEY_TOKEN); } + public static String getToken() { + return sToken; + } + public static boolean hasToken() { return MMKV.defaultMMKV().containsKey(KEY_TOKEN); } diff --git a/app/src/main/java/com/xuexiang/xuidemo/utils/sdkinit/ANRWatchDogInit.java b/app/src/main/java/com/xuexiang/xuidemo/utils/sdkinit/ANRWatchDogInit.java new file mode 100644 index 00000000..519d1a29 --- /dev/null +++ b/app/src/main/java/com/xuexiang/xuidemo/utils/sdkinit/ANRWatchDogInit.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xuexiang.xuidemo.utils.sdkinit; + +import com.github.anrwatchdog.ANRWatchDog; +import com.xuexiang.xutil.common.logger.Logger; + +/** + * ANR看门狗监听器初始化 + * + * @author xuexiang + * @since 2020-02-18 15:08 + */ +public final class ANRWatchDogInit { + + private static final String TAG = "ANRWatchDog"; + + private ANRWatchDogInit() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * ANR看门狗 + */ + private static ANRWatchDog sANRWatchDog; + + /** + * ANR监听触发的时间 + */ + private static final int ANR_DURATION = 4000; + + + /** + * ANR静默处理【就是不处理,直接记录一下日志】 + */ + private final static ANRWatchDog.ANRListener SILENT_LISTENER = error -> Logger.eTag(TAG, error); + + /** + * ANR自定义处理【可以是记录日志用于上传】 + */ + private final static ANRWatchDog.ANRListener CUSTOM_LISTENER = error -> { + Logger.eTag(TAG, "Detected Application Not Responding!", error); + //这里进行ANR的捕获后的操作 + + throw error; + }; + + /** + * 初始化并启动看门狗 + */ + public static void init() { + //这里设置监听的间隔为2秒 + sANRWatchDog = new ANRWatchDog(2000); + sANRWatchDog.setANRInterceptor(duration -> { + long ret = ANR_DURATION - duration; + if (ret > 0) { + Logger.wTag(TAG, "Intercepted ANR that is too short (" + duration + " ms), postponing for " + ret + " ms."); + } + //当返回是0或者负数时,就会触发ANR监听回调 + return ret; + }).setANRListener(CUSTOM_LISTENER).start(); + Logger.d("ANR看门狗监听器启动..."); + } + + public static ANRWatchDog getANRWatchDog() { + return sANRWatchDog; + } +} diff --git a/app/src/main/res/drawable-xhdpi/img_ali_pay.png b/app/src/main/res/drawable-xhdpi/img_ali_pay.png new file mode 100644 index 00000000..3a47e18d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_ali_pay.png differ diff --git a/app/src/main/res/drawable-xhdpi/img_wei_xin_pay.png b/app/src/main/res/drawable-xhdpi/img_wei_xin_pay.png new file mode 100644 index 00000000..0634ac36 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_wei_xin_pay.png differ diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index 1a6cf909..ad221ccf 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -37,6 +37,12 @@ android:layout_marginStart="@dimen/spacing_16" android:text="复杂相机" /> + + diff --git a/app/src/main/res/layout/fragment_camerakit.xml b/app/src/main/res/layout/fragment_camerakit.xml new file mode 100644 index 00000000..35e9a9c3 --- /dev/null +++ b/app/src/main/res/layout/fragment_camerakit.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_sponsor.xml b/app/src/main/res/layout/fragment_sponsor.xml new file mode 100644 index 00000000..5a4e9afe --- /dev/null +++ b/app/src/main/res/layout/fragment_sponsor.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa9695c7..4f111ace 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,9 +15,22 @@ 访问官网 说明文档 版本更新 + 赞助项目 GitHub主页 © %1$s xuexiangjys All rights reserved. 加入QQ官方交流群 +     你的打赏是我维护的动力,我将会列出所有打赏人员的清单在Github上作为凭证,打赏前请留下打赏项目的备注! + + + 退出应用 + 同意 + 不同意 + 再次查看 + 仍不同意 + 温馨提示 + 要不要再想想 + 我们非常重视对你个人信息的保护,承诺严格按照《%s隐私权政策》保护及处理你的信息。如果你不同意该政策,很遗憾我们将无法为你提供服务 + 《%s隐私权政策》 Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. diff --git a/docs/README.md b/docs/README.md index 88a36a82..7f287e4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ ## X系列库快速集成 -为了方便大家快速集成X系列框架库,我提供了一个空壳模版供大家参考使用: https://github.com/xuexiangjys/TemplateAppProject +为了方便大家快速集成X系列框架库,我提供了一个空壳模版供大家参考使用: [https://github.com/xuexiangjys/TemplateAppProject](https://github.com/xuexiangjys/TemplateAppProject) ---- @@ -57,12 +57,12 @@ allprojects { dependencies { ... //androidx项目 - implementation 'com.github.xuexiangjys:XUI:1.1.2' + implementation 'com.github.xuexiangjys:XUI:1.1.3' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'com.google.android.material:material:1.1.0-beta01' - implementation 'com.github.bumptech.glide:glide:4.8.0' + implementation 'com.github.bumptech.glide:glide:4.11.0' } ``` diff --git a/versions.gradle b/versions.gradle index b3154caa..ccf1532e 100644 --- a/versions.gradle +++ b/versions.gradle @@ -22,7 +22,7 @@ versions.mockito = "2.7.19" versions.mockito_all = "1.10.19" versions.dexmaker = "2.2.0" versions.constraint_layout = "1.1.3" -versions.glide = "4.8.0" +versions.glide = "4.11.0" versions.timber = "4.5.1" versions.rxjava2 = "2.1.3" versions.rx_android = "2.0.1" diff --git a/xui_lib/src/main/java/com/xuexiang/xui/widget/activity/BaseSplashActivity.java b/xui_lib/src/main/java/com/xuexiang/xui/widget/activity/BaseSplashActivity.java index ab76451c..c259ab20 100644 --- a/xui_lib/src/main/java/com/xuexiang/xui/widget/activity/BaseSplashActivity.java +++ b/xui_lib/src/main/java/com/xuexiang/xui/widget/activity/BaseSplashActivity.java @@ -19,7 +19,7 @@ public abstract class BaseSplashActivity extends AppCompatActivity { /** * 默认启动页过渡时间 */ - private static final int DEFAULT_SPLASH_DURATION_MILLIS = 2000; + private static final int DEFAULT_SPLASH_DURATION_MILLIS = 500; protected LinearLayout mWelcomeLayout; diff --git a/xui_lib/src/main/java/com/xuexiang/xui/widget/dialog/materialdialog/MaterialDialog.java b/xui_lib/src/main/java/com/xuexiang/xui/widget/dialog/materialdialog/MaterialDialog.java index 345a8766..adcddbe4 100755 --- a/xui_lib/src/main/java/com/xuexiang/xui/widget/dialog/materialdialog/MaterialDialog.java +++ b/xui_lib/src/main/java/com/xuexiang/xui/widget/dialog/materialdialog/MaterialDialog.java @@ -58,13 +58,13 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.UiThread; -import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.xuexiang.xui.R; import com.xuexiang.xui.XUI; +import com.xuexiang.xui.utils.ResUtils; import com.xuexiang.xui.utils.ThemeUtils; import com.xuexiang.xui.widget.dialog.materialdialog.internal.MDButton; import com.xuexiang.xui.widget.dialog.materialdialog.internal.MDRootLayout; @@ -80,9 +80,6 @@ import java.util.List; import java.util.Locale; -import java.lang.String; -import java.lang.CharSequence; - /** * Material Design Dialog @@ -307,8 +304,7 @@ public boolean onItemSelected( final Drawable getListSelector() { if (builder.listSelector != 0) { - return ResourcesCompat.getDrawable( - builder.context.getResources(), builder.listSelector, null); + return ResUtils.getDrawable(builder.context, builder.listSelector); } final Drawable d = ThemeUtils.resolveDrawable(builder.context, R.attr.md_list_selector); if (d != null) { @@ -335,8 +331,7 @@ public void setPromptCheckBoxChecked(boolean checked) { /* package */ Drawable getButtonSelector(DialogAction which, boolean isStacked) { if (isStacked) { if (builder.btnSelectorStacked != 0) { - return ResourcesCompat.getDrawable( - builder.context.getResources(), builder.btnSelectorStacked, null); + return ResUtils.getDrawable(builder.context, builder.btnSelectorStacked); } final Drawable d = ThemeUtils.resolveDrawable(builder.context, R.attr.md_btn_stacked_selector); @@ -349,8 +344,7 @@ public void setPromptCheckBoxChecked(boolean checked) { switch (which) { case NEUTRAL: if (builder.btnSelectorNeutral != 0) { - return ResourcesCompat.getDrawable( - builder.context.getResources(), builder.btnSelectorNeutral, null); + return ResUtils.getDrawable(builder.context, builder.btnSelectorNeutral); } d = ThemeUtils.resolveDrawable(builder.context, R.attr.md_btn_neutral_selector); if (d != null) { @@ -363,8 +357,7 @@ public void setPromptCheckBoxChecked(boolean checked) { break; case NEGATIVE: if (builder.btnSelectorNegative != 0) { - return ResourcesCompat.getDrawable( - builder.context.getResources(), builder.btnSelectorNegative, null); + return ResUtils.getDrawable(builder.context, builder.btnSelectorNegative); } d = ThemeUtils.resolveDrawable(builder.context, R.attr.md_btn_negative_selector); if (d != null) { @@ -377,8 +370,7 @@ public void setPromptCheckBoxChecked(boolean checked) { break; default: if (builder.btnSelectorPositive != 0) { - return ResourcesCompat.getDrawable( - builder.context.getResources(), builder.btnSelectorPositive, null); + return ResUtils.getDrawable(builder.context, builder.btnSelectorPositive); } d = ThemeUtils.resolveDrawable(builder.context, R.attr.md_btn_positive_selector); if (d != null) { @@ -1504,7 +1496,7 @@ public Builder icon(@NonNull Drawable icon) { public Builder iconRes(@DrawableRes int icon) { if (icon != -1) { - this.icon = ResourcesCompat.getDrawable(context.getResources(), icon, null); + this.icon = ResUtils.getDrawable(context, icon); } return this; } diff --git a/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/AlignEnum.java b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/AlignEnum.java new file mode 100644 index 00000000..697b8d1f --- /dev/null +++ b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/AlignEnum.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xuexiang.xui.widget.imageview.strategy; + +/** + * 对齐方式 + * + * @author xuexiang + * @since 2020/3/2 12:20 PM + */ +public enum AlignEnum { + + /** + * 默认方式 + */ + DEFAULT, + + /** + * 中心裁剪(将图片放在ImageView的中心点,然后对图片进行等比例缩放,等比例缩放图片的宽和高均不小于控件对应的宽高) + */ + CENTER_CROP, + + /** + * 中心填充(将图片放在ImageView的中心点,然后对图片进行等比例缩放,等比例缩放图片的宽和高均不大于控件对应的宽高) + */ + CENTER_INSIDE, + + /** + * 中心适应(将图片放在ImageView的中心点,对图片进行等比例缩放从而完整地显示图片,使得图片的宽高中至少有一个值恰好等于控件的宽或者高) + */ + FIT_CENTER, + + /** + * 圆形裁剪 + */ + CIRCLE_CROP, + + +} diff --git a/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/LoadOption.java b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/LoadOption.java index 28636772..8e77da78 100644 --- a/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/LoadOption.java +++ b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/LoadOption.java @@ -26,6 +26,11 @@ * @since 2019-11-09 11:12 */ public class LoadOption { + + /** + * 默认加载的超时时间(2500ms) + */ + public static final int DEFAULT_TIMEOUT = 2500; /** * 磁盘缓存策略 */ @@ -34,6 +39,10 @@ public class LoadOption { * 占位图 */ public Drawable placeholder; + /** + * 出错时显示的图片 + */ + public Drawable error; /** * 宽度 */ @@ -42,6 +51,14 @@ public class LoadOption { * 高度 */ public int height; + /** + * 对齐方式 + */ + public AlignEnum align = AlignEnum.DEFAULT; + /** + * 加载超时时间 + */ + public int timeoutMs = DEFAULT_TIMEOUT; public static LoadOption of(DiskCacheStrategyEnum cacheStrategy) { return new LoadOption(cacheStrategy); @@ -105,6 +122,33 @@ public int getWidth() { return width; } + public AlignEnum getAlign() { + return align; + } + + public LoadOption setAlign(AlignEnum align) { + this.align = align; + return this; + } + + public int getTimeoutMs() { + return timeoutMs; + } + + public LoadOption setTimeoutMs(int timeoutMs) { + this.timeoutMs = timeoutMs; + return this; + } + + public Drawable getError() { + return error; + } + + public LoadOption setError(Drawable error) { + this.error = error; + return this; + } + @Override public String toString() { return "LoadOption{" + diff --git a/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/impl/GlideImageLoadStrategy.java b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/impl/GlideImageLoadStrategy.java index ae65a156..57ad754a 100644 --- a/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/impl/GlideImageLoadStrategy.java +++ b/xui_lib/src/main/java/com/xuexiang/xui/widget/imageview/strategy/impl/GlideImageLoadStrategy.java @@ -71,6 +71,7 @@ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, DataSource dataSource, boolean isFirstResource) { listener.onLoadSuccess(); @@ -234,6 +235,7 @@ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, DataSource dataSource, boolean isFirstResource) { listener.onLoadSuccess(); @@ -265,6 +267,7 @@ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, DataSource dataSource, boolean isFirstResource) { listener.onLoadSuccess(); @@ -275,6 +278,12 @@ public boolean onResourceReady(GifDrawable resource, Object model, Target - + @@ -1217,7 +1217,7 @@ - + @@ -1389,7 +1389,7 @@ - + @@ -1544,7 +1544,7 @@ - + @@ -1607,7 +1607,7 @@ - + @@ -1682,7 +1682,10 @@ - + + + + diff --git a/xui_lib/src/main/res/values/xui_styles_widget.xml b/xui_lib/src/main/res/values/xui_styles_widget.xml index c19cefbb..3e95c838 100644 --- a/xui_lib/src/main/res/values/xui_styles_widget.xml +++ b/xui_lib/src/main/res/values/xui_styles_widget.xml @@ -18,8 +18,8 @@ + * MaterialDialog * + **********************************************-->