diff --git a/app/build.gradle b/app/build.gradle index 5b86437..a43ba80 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { minSdk 24 targetSdk 32 versionCode 12 - versionName "1.2" + versionName "1.3" resValue "string", "weathericons_version", "${versionName}" diff --git a/app/src/main/java/cc/niushuai/dididone/biz/BizGlobal.java b/app/src/main/java/cc/niushuai/dididone/biz/BizGlobal.java index 53f5dce..5a2a77c 100644 --- a/app/src/main/java/cc/niushuai/dididone/biz/BizGlobal.java +++ b/app/src/main/java/cc/niushuai/dididone/biz/BizGlobal.java @@ -46,6 +46,8 @@ public class BizGlobal { public static final String EMPTY_PROJECT_TIPS = "先去添加打卡项吧~"; public static final String EMPTY_PROJECT_TIPS_ICON = "cmd_alert_decagram_outline"; public static final int REQUEST_CODE_GENERAL = 1; + public static final Integer REQUEST_CODE_BACKUP = 10005; + public static final Integer REQUEST_CODE_RESTORE = 10006; private static final String URI_SCHEMA_FILE = "file"; private static final String URI_SCHEMA_CONTENT = "content"; diff --git a/app/src/main/java/cc/niushuai/dididone/biz/dao/RecordDao.java b/app/src/main/java/cc/niushuai/dididone/biz/dao/RecordDao.java index 3f3f730..cef646b 100644 --- a/app/src/main/java/cc/niushuai/dididone/biz/dao/RecordDao.java +++ b/app/src/main/java/cc/niushuai/dididone/biz/dao/RecordDao.java @@ -30,6 +30,9 @@ public interface RecordDao { @Query("select * from t_record where deleted = 0 and create_date >= :startDate and create_date <= :endDate order by check_date desc, create_date desc") Flowable> queryByDate(long startDate, long endDate); + @Insert + Completable insertAll(List records); + @Insert Completable insertAll(Record... records); diff --git a/app/src/main/java/cc/niushuai/dididone/ui/setting/SettingFragment.java b/app/src/main/java/cc/niushuai/dididone/ui/setting/SettingFragment.java index 83d39dc..7652064 100644 --- a/app/src/main/java/cc/niushuai/dididone/ui/setting/SettingFragment.java +++ b/app/src/main/java/cc/niushuai/dididone/ui/setting/SettingFragment.java @@ -1,6 +1,7 @@ package cc.niushuai.dididone.ui.setting; import android.Manifest; +import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -15,6 +16,7 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.FileNotFoundException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -33,12 +35,17 @@ import cc.niushuai.dididone.ui.base.BaseFragment; import cc.niushuai.dididone.util.GsonUtil; import cc.niushuai.dididone.util.Toasts; import cc.niushuai.dididone.util.XLog; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; public class SettingFragment extends BaseFragment { @@ -66,32 +73,38 @@ public class SettingFragment extends BaseFragment { } private void addRestoreClickListener() { - + binding.sSetRestore.setOnClickListener(view -> { + requestForPermission(BizGlobal.REQUEST_CODE_RESTORE, Intent.ACTION_GET_CONTENT); + }); } private void addBackupClickListener() { binding.sSetBackup.setOnClickListener(view -> { - if ( - // true表示未授权读权限 - ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED || - // true表示未授权写权限 - ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - // 申请授权 - if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) - || ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) - ) { - Toasts.longShow(getContext(), "您曾经选择过禁止弹窗授权, 请手动进入应用管理授权"); - } else { - ActivityCompat.requestPermissions(getActivity(), new String[]{ - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - }, BizGlobal.REQUEST_CODE_GENERAL); - } + requestForPermission(BizGlobal.REQUEST_CODE_BACKUP, Intent.ACTION_CREATE_DOCUMENT); + }); + } + + private void requestForPermission(Integer requestCode, String action) { + if ( + // true表示未授权读权限 + ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED || + // true表示未授权写权限 + ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + // 申请授权 + if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + || ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) + ) { + Toasts.longShow(getContext(), "您曾经选择过禁止弹窗授权, 请手动进入应用管理授权"); } else { - chooseBackupFileLocation(); + ActivityCompat.requestPermissions(getActivity(), new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + }, BizGlobal.REQUEST_CODE_GENERAL); } - }); + } else { + chooseBackupFileLocation(requestCode, action); + } } @Override @@ -99,39 +112,47 @@ public class SettingFragment extends BaseFragment { boolean isAllGranted = false; - switch (requestCode) { - case BizGlobal.REQUEST_CODE_GENERAL: - for (int i = 0; i < permissions.length; i++) { - XLog.d("permission: {} result: {}", permissions[i], grantResults[i]); + for (int i = 0; i < permissions.length; i++) { + XLog.d("permission: {} result: {}", permissions[i], grantResults[i]); - if (grantResults[i] == PackageManager.PERMISSION_DENIED) { - isAllGranted = false; + if (grantResults[i] == PackageManager.PERMISSION_DENIED) { + isAllGranted = false; // break; - } - } - - break; - default: - XLog.d("un case requestCode: {}", requestCode); + } } if (isAllGranted) { // 授权通过时 打开文件保存逻辑 - chooseBackupFileLocation(); + + String action = null; + + if (BizGlobal.REQUEST_CODE_BACKUP.equals(requestCode)) { + action = Intent.ACTION_CREATE_DOCUMENT; + } else if (BizGlobal.REQUEST_CODE_RESTORE.equals(requestCode)) { + action = Intent.ACTION_GET_CONTENT; + } else { + XLog.d("unknown request code: {}", requestCode); + Toasts.shortShow(getContext(), "未知的请求码: {}", requestCode + ""); + return; + } + + chooseBackupFileLocation(requestCode, action); } } - private void chooseBackupFileLocation() { + private void chooseBackupFileLocation(Integer requestCode, String action) { // 写文件 - Intent chooseFileLocationIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + Intent chooseFileLocationIntent = new Intent(action); chooseFileLocationIntent.addCategory(Intent.CATEGORY_OPENABLE); chooseFileLocationIntent.setType("*/*"); - chooseFileLocationIntent.putExtra(Intent.EXTRA_TITLE, StrUtil.format(FILE_NAME, getString(R.string.app_name), DateUtil.date().toString(DatePattern.PURE_DATETIME_PATTERN))); + if (BizGlobal.REQUEST_CODE_BACKUP.equals(requestCode)) { + chooseFileLocationIntent.putExtra(Intent.EXTRA_TITLE, StrUtil.format(FILE_NAME, getString(R.string.app_name), DateUtil.date().toString(DatePattern.PURE_DATETIME_PATTERN))); + } // 选择文件 在onActivityResult中监听选择的文件 - startActivityForResult(chooseFileLocationIntent, BizGlobal.REQUEST_CODE_GENERAL); + startActivityForResult(chooseFileLocationIntent, requestCode); } private String dealBackupContent(List recordList) { @@ -144,33 +165,131 @@ public class SettingFragment extends BaseFragment { projectRecordList.add(new ProjectRecord(allProjectMap.get(projectId), collect)); } // JSON化 - return GsonUtil.toJsonString(projectRecordList); + return GsonUtil.toJson(projectRecordList); } @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == BizGlobal.REQUEST_CODE_GENERAL && requestCode == 1) { - Uri fileUri = data.getData(); - - Single> listSingle = DBManager.INSTANCE.recordDao().xListAll(); - listSingle.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe((recordList, throwable) -> { - XLog.d("addBackClickListener == " + recordList.size()); - - try { - String content = dealBackupContent(recordList); - OutputStream outputStream = getContext().getContentResolver().openOutputStream(fileUri); - IoUtil.write(outputStream, true, content.getBytes(StandardCharsets.UTF_8)); - - Toasts.shortShow(getContext(), "文件已备份: {}", fileUri.getPath().substring(fileUri.getPath().indexOf(":")).replace(":", "/sdcard/")); - } catch (FileNotFoundException e) { - XLog.d("save file【{}】 error: {}", fileUri.getPath(), e.getMessage(), e); - } - }); + if (resultCode != Activity.RESULT_OK) { + Toasts.shortShow(getContext(), "操作失败 {} --> {}", requestCode + "", resultCode + ""); + return; + } + if (BizGlobal.REQUEST_CODE_BACKUP.equals(requestCode)) { + backupResult(data); + } else if (BizGlobal.REQUEST_CODE_RESTORE.equals(requestCode)) { + restoreResult(data); } } + private void restoreResult(Intent data) { + + try { + Uri uri = data.getData(); + InputStream inputStream = getContext().getContentResolver().openInputStream(uri); + + String readStr = IoUtil.read(inputStream, StandardCharsets.UTF_8); + + List> list = GsonUtil.toBean(readStr, List.class); + if (CollUtil.isEmpty(list)) { + Toasts.shortShow(getContext(), "未发现可导入内容, 请检查文件是否正确"); + return; + } + + List projectRecordList = new ArrayList<>(list.size()); + for (Map map : list) { + String json = GsonUtil.toJson(map); + ProjectRecord projectRecord = GsonUtil.toBean(json, ProjectRecord.class); + projectRecordList.add(projectRecord); + } + + restoreProjectRecord(projectRecordList); + + } catch (FileNotFoundException e) { + XLog.d("文件读取失败: {}", e.getMessage(), e); + Toasts.shortShow(getContext(), "文件读取失败: {}", e.getMessage()); + } + } + + private void restoreProjectRecord(List projectRecordList) { + + if (CollUtil.isNotEmpty(projectRecordList)) { + for (ProjectRecord projectRecord : projectRecordList) { + + List recordList = projectRecord.getRecordList(); + if (CollUtil.isNotEmpty(recordList)) { + // 插入record + Completable completable = DBManager.INSTANCE.recordDao().insertAll(recordList); + completable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new CompletableObserver() { + @Override + public void onSubscribe(Disposable d) { + XLog.d("restore onSubscribe"); + } + + @Override + public void onComplete() { + XLog.d("restore onComplete"); + BizGlobal.buildCache(); + } + + @Override + public void onError(Throwable e) { + XLog.d("restore onError: {}", e.getMessage(), e); + } + }); + } + + // 插入project + Project project = new Project(); + BeanUtil.copyProperties(projectRecord, project); + Completable completable = DBManager.INSTANCE.projectDao().insertAll(project); + completable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new CompletableObserver() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onComplete() { + BizGlobal.buildCache(); + } + + @Override + public void onError(Throwable e) { + + } + }); + } + + } + + + } + + private void backupResult(@NonNull Intent data) { + Uri fileUri = data.getData(); + + Single> listSingle = DBManager.INSTANCE.recordDao().xListAll(); + listSingle.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe((recordList, throwable) -> { + XLog.d("addBackClickListener == " + recordList.size()); + + try { + String content = dealBackupContent(recordList); + OutputStream outputStream = getContext().getContentResolver().openOutputStream(fileUri); + IoUtil.write(outputStream, true, content.getBytes(StandardCharsets.UTF_8)); + + Toasts.shortShow(getContext(), "文件已备份: {}", fileUri.getPath().substring(fileUri.getPath().indexOf(":")).replace(":", "/sdcard/")); + } catch (FileNotFoundException e) { + XLog.d("save file【{}】 error: {}", fileUri.getPath(), e.getMessage(), e); + } + }); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/java/cc/niushuai/dididone/util/GsonUtil.java b/app/src/main/java/cc/niushuai/dididone/util/GsonUtil.java index 5a2386f..6bd8d18 100644 --- a/app/src/main/java/cc/niushuai/dididone/util/GsonUtil.java +++ b/app/src/main/java/cc/niushuai/dididone/util/GsonUtil.java @@ -45,14 +45,14 @@ public class GsonUtil { /** * 根据对象返回json 过滤空值字段 */ - public static String toJsonStringIgnoreNull(Object object) { + public static String toJsonIgnoreNull(Object object) { return GSON.toJson(object); } /** * 根据对象返回json 不过滤空值字段 */ - public static String toJsonString(Object object) { + public static String toJson(Object object) { return GSON_NULL.toJson(object); } @@ -65,7 +65,7 @@ public class GsonUtil { * @param * @return */ - public static T strToJavaBean(String json, Class classOfT) { + public static T toBean(String json, Class classOfT) { return GSON.fromJson(json, classOfT); } @@ -86,7 +86,7 @@ public class GsonUtil { * @param cls * @return */ - public static List strToList(String gsonString, Class cls) { + public static List toList(String gsonString, Class cls) { return GSON.fromJson(gsonString, new TypeToken>() { }.getType()); } @@ -97,7 +97,7 @@ public class GsonUtil { * @param gsonString * @return */ - public static List> strToListMaps(String gsonString) { + public static List> toListMap(String gsonString) { return GSON.fromJson(gsonString, new TypeToken>>() { }.getType()); } @@ -108,7 +108,7 @@ public class GsonUtil { * @param gsonString * @return */ - public static Map strToMaps(String gsonString) { + public static Map toMap(String gsonString) { return GSON.fromJson(gsonString, new TypeToken>() { }.getType()); }