diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c386e21..9e05d19 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "com.haodustudio.DailyNotes" minSdk = 24 targetSdk = 33 - versionCode = 5 - versionName = "1.0.4" + versionCode = 6 + versionName = "1.0.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -68,6 +68,7 @@ dependencies { implementation("com.squareup.retrofit2:converter-gson:2.6.2") implementation("de.hdodenhof:circleimageview:3.1.0") implementation("com.google.android.material:material:1.11.0") + implementation("io.sentry:sentry-android:7.13.0") } sentry { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2c7c11..98cc103 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,9 @@ + diff --git a/app/src/main/java/com/haodustudio/DailyNotes/BaseApplication.kt b/app/src/main/java/com/haodustudio/DailyNotes/BaseApplication.kt index 6e6119f..42d57f2 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/BaseApplication.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/BaseApplication.kt @@ -13,8 +13,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.utils.BitmapUtils import com.haodustudio.DailyNotes.viewModel.viewModels.GlobalViewModel +import io.sentry.Sentry +import io.sentry.android.core.SentryAndroid import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -29,6 +32,8 @@ class BaseApplication : Application(), ViewModelStoreOwner { override val viewModelStore: ViewModelStore get() = appViewModelStore + private var sentryInitialized = false + companion object { lateinit var instance: BaseApplication private set @@ -135,6 +140,27 @@ class BaseApplication : Application(), ViewModelStoreOwner { NOTES_PATH = filesDir.absolutePath + "/notes/" TEMPLATE_DOWNLOAD_FROM_URI_PATH = filesDir.absolutePath + "/uri_template/" BACKGROUND_DOWNLOAD_FROM_URI_PATH = filesDir.absolutePath + "/uri_background/" + + updateSentryState(PrivacySettingsManager.isCrashReportingEnabled()) + } + + fun updateSentryState(enabled: Boolean) { + if (enabled) { + if (!sentryInitialized) { + runCatching { + SentryAndroid.init(this) + }.onSuccess { + sentryInitialized = true + }.onFailure { + sentryInitialized = false + } + } + } else { + if (sentryInitialized) { + Sentry.close() + sentryInitialized = false + } + } } override fun onTerminate() { diff --git a/app/src/main/java/com/haodustudio/DailyNotes/helper/PrivacySettingsManager.kt b/app/src/main/java/com/haodustudio/DailyNotes/helper/PrivacySettingsManager.kt new file mode 100644 index 0000000..ef7f44e --- /dev/null +++ b/app/src/main/java/com/haodustudio/DailyNotes/helper/PrivacySettingsManager.kt @@ -0,0 +1,43 @@ +package com.haodustudio.DailyNotes.helper + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import com.haodustudio.DailyNotes.BaseApplication + +object PrivacySettingsManager { + private const val KEY_CRASH_REPORTING = "privacy_crash_reporting_enabled" + private const val KEY_LOCATION = "privacy_location_enabled" + private const val KEY_CLOUD_RESOURCES = "privacy_cloud_resources_enabled" + + private val prefs: SharedPreferences by lazy { + BaseApplication.instance.getSharedPreferences( + BaseApplication.APP_SHARED_PREFERENCES_NAME, + Context.MODE_PRIVATE + ) + } + + fun isCrashReportingEnabled(): Boolean = prefs.getBoolean(KEY_CRASH_REPORTING, true) + + fun updateCrashReportingEnabled(enabled: Boolean) { + prefs.edit { putBoolean(KEY_CRASH_REPORTING, enabled) } + BaseApplication.instance.updateSentryState(enabled) + } + + fun isLocationEnabled(): Boolean = prefs.getBoolean(KEY_LOCATION, true) + + fun updateLocationEnabled(enabled: Boolean) { + prefs.edit { + putBoolean(KEY_LOCATION, enabled) + if (!enabled && prefs.getBoolean("app_background_is_weather", false)) { + putBoolean("app_background_is_weather", false) + } + } + } + + fun isCloudResourcesEnabled(): Boolean = prefs.getBoolean(KEY_CLOUD_RESOURCES, true) + + fun updateCloudResourcesEnabled(enabled: Boolean) { + prefs.edit { putBoolean(KEY_CLOUD_RESOURCES, enabled) } + } +} diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/AboutSoftware.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/AboutSoftware.kt index 02303b6..2fd0158 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/AboutSoftware.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/AboutSoftware.kt @@ -9,8 +9,9 @@ import com.haodustudio.DailyNotes.BaseApplication import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.databinding.ActivityAboutSoftwareBinding import com.haodustudio.DailyNotes.helper.makeToast -import com.haodustudio.DailyNotes.view.activities.ViewImage import com.haodustudio.DailyNotes.view.activities.base.BaseActivity +import com.haodustudio.DailyNotes.view.activities.ViewImage +import com.haodustudio.DailyNotes.view.activities.PrivacySettingsActivity class AboutSoftware : BaseActivity() { private val binding by lazy { ActivityAboutSoftwareBinding.inflate(layoutInflater) } @@ -90,7 +91,7 @@ class AboutSoftware : BaseActivity() { icon.setImageResource(R.drawable.ic_about_privacy) root.contentDescription = getString(R.string.about_section_privacy_desc) root.setOnClickListener { - makeToast(getString(R.string.about_feature_placeholder)) + startActivity(Intent(this@AboutSoftware, PrivacySettingsActivity::class.java)) } } } diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/BackgroundChooser.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/BackgroundChooser.kt index 4d05816..eff2abf 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/BackgroundChooser.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/BackgroundChooser.kt @@ -1,11 +1,8 @@ package com.haodustudio.DailyNotes.view.activities import android.graphics.Bitmap -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.util.Log import android.view.LayoutInflater -import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.ViewModelProvider @@ -15,142 +12,207 @@ import com.bumptech.glide.request.transition.Transition import com.haodustudio.DailyNotes.BaseApplication import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.databinding.ActivityBackgroundChooserBinding +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.helper.makeToast -import com.haodustudio.DailyNotes.helper.toBoolean import com.haodustudio.DailyNotes.model.models.BackgroundList import com.haodustudio.DailyNotes.utils.FileUtils +import com.haodustudio.DailyNotes.view.activities.base.BaseActivity import com.haodustudio.DailyNotes.viewModel.repositories.NetworkRepository import com.haodustudio.DailyNotes.viewModel.viewModels.GlobalViewModel import retrofit2.Call import retrofit2.Callback import retrofit2.Response -class BackgroundChooser : AppCompatActivity() { +class BackgroundChooser : BaseActivity(noShot = true) { + private val binding by lazy { ActivityBackgroundChooserBinding.inflate(layoutInflater) } - private val appViewModel = ViewModelProvider( - BaseApplication.instance, - ViewModelProvider.AndroidViewModelFactory.getInstance(BaseApplication.instance) - )[GlobalViewModel::class.java] + private val appViewModel by lazy { + ViewModelProvider( + BaseApplication.instance, + ViewModelProvider.AndroidViewModelFactory.getInstance(BaseApplication.instance) + )[GlobalViewModel::class.java] + } + private val inflater by lazy { LayoutInflater.from(this) } + private val remoteItems = mutableSetOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - - val backgrounds = ArrayList() try { - backgrounds.add( - BaseApplication.ASSETS_WEATHER_BACKGROUND_PATH + assets.list( - FileUtils.removePathSlashAtLast(BaseApplication.ASSETS_WEATHER_BACKGROUND_PATH) - )!![0] - ) - backgrounds.addAll( - assets.list( - FileUtils.removePathSlashAtLast(BaseApplication.ASSETS_DEFAULT_BACKGROUND_PATH) - )!!.toMutableList().map { - BaseApplication.ASSETS_DEFAULT_BACKGROUND_PATH + it - } - ) + renderLocalBackgrounds() + loadRemoteBackgroundsIfNeeded() + } catch (error: Exception) { + error.printStackTrace() + makeToast("Load background failure") + finish() + } + } + + private fun renderLocalBackgrounds() { + val locationEnabled = PrivacySettingsManager.isLocationEnabled() + val locals = mutableListOf() - for (i in 0 until backgrounds.size) { - val it = backgrounds[i] - val view = LayoutInflater.from(this).inflate(R.layout.app_background_item, binding.listFather, false) - Glide.with(this).load("file:///android_asset/$it").into( - view.findViewById(R.id.background_image) + if (locationEnabled) { + val weatherAssets = assets.list( + FileUtils.removePathSlashAtLast(BaseApplication.ASSETS_WEATHER_BACKGROUND_PATH) + )?.sorted() + val weatherItem = weatherAssets?.firstOrNull() + if (weatherItem != null) { + locals.add( + LocalBackground( + assetPath = BaseApplication.ASSETS_WEATHER_BACKGROUND_PATH + weatherItem, + isWeather = true, + displayName = getString(R.string.privacy_weather_option_label) + ) ) - val mTv = view.findViewById(R.id.background_name) - if (i == 0) { - mTv.text = "随天气变化" - }else { - mTv.text = "背景 $i" - } + } + } else { + makeToast(getString(R.string.privacy_location_disabled_hint)) + } - val isWeather = !i.toBoolean() - view.setOnClickListener { _ -> - Log.d("BackgroundChooser", "Choose background $it") - try { - appViewModel.setAppBackground(isWeather, "file:///android_asset/$it") - }catch (e: Exception) { - e.printStackTrace() - makeToast("Change failure") - }finally { - finish() + val defaultAssets = assets.list( + FileUtils.removePathSlashAtLast(BaseApplication.ASSETS_DEFAULT_BACKGROUND_PATH) + )?.filter { it.isNotEmpty() && !it.startsWith(".") } + ?.sorted() + ?.distinct() + + defaultAssets?.forEachIndexed { index, assetName -> + locals.add( + LocalBackground( + assetPath = BaseApplication.ASSETS_DEFAULT_BACKGROUND_PATH + assetName, + isWeather = false, + displayName = getString(R.string.privacy_background_option_label, index + 1) + ) + ) + } + + // 使用 distinctBy 确保没有重复的路径 + locals.distinctBy { it.assetPath }.forEach { addAssetBackground(it) } + } + + private fun addAssetBackground(item: LocalBackground) { + val view = inflater.inflate(R.layout.app_background_item, binding.listFather, false) + val imageView = view.findViewById(R.id.background_image) + val nameView = view.findViewById(R.id.background_name) + + Glide.with(this) + .load("file:///android_asset/${item.assetPath}") + .into(imageView) + + nameView.text = item.displayName + + view.setOnClickListener { + setBackgroundAndFinish(item.isWeather, "file:///android_asset/${item.assetPath}") + } + + binding.listFather.addView(view) + } + + private fun setBackgroundAndFinish(isWeather: Boolean, path: String) { + try { + appViewModel.setAppBackground(isWeather, path) + } catch (error: Exception) { + error.printStackTrace() + makeToast("Change failure") + } finally { + finish() + } + } + + private fun loadRemoteBackgroundsIfNeeded() { + if (!PrivacySettingsManager.isCloudResourcesEnabled()) { + makeToast(getString(R.string.privacy_cloud_disabled_hint)) + return + } + + makeToast("加载网络背景") + val call = NetworkRepository.getBackgroundListCall() + if (call == null) { + makeToast("无网络连接") + return + } + + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + try { + val list = response.body()?.getList()?.mapNotNull { BaseApplication.buildServerUrl(it) } + if (list.isNullOrEmpty()) { + makeToast("无网络连接") + return } + FileUtils.makeRootDirectory(BaseApplication.BACKGROUND_DOWNLOAD_FROM_URI_PATH) + makeToast("获取成功") + list.forEach { downloadRemoteBackground(it) } + } catch (error: Exception) { + onFailure(call, error) } - binding.listFather.addView(view) } - makeToast("加载网络背景") - val call = NetworkRepository.getBackgroundListCall() - if (call == null) { + override fun onFailure(call: Call, t: Throwable) { + t.printStackTrace() makeToast("无网络连接") - } else { - call.enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { + } + }) + } + + private fun downloadRemoteBackground(uri: String) { + Glide.with(this).asBitmap().load(uri) + .into(object : SimpleTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { try { - val obj = response.body()!! - val realUriList = obj.getList().mapNotNull { BaseApplication.buildServerUrl(it) } - if (realUriList.isEmpty()) { - makeToast("无网络连接") + val fileName = extractFileName(uri) + if (!remoteItems.add(fileName)) { return } - if (!FileUtils.exists(BaseApplication.BACKGROUND_DOWNLOAD_FROM_URI_PATH)) { - FileUtils.makeRootDirectory(BaseApplication.BACKGROUND_DOWNLOAD_FROM_URI_PATH) - } - makeToast("获取成功") - realUriList.forEach { - Glide.with(this@BackgroundChooser).asBitmap().load(it).into(object : SimpleTarget() { - override fun onResourceReady( - resource: Bitmap, - transition: Transition? - ) { - try { - val fileName = it.substring(it.lastIndexOf("/") + 1, it.length) - val cpPath = cacheDir.absolutePath + '/' + fileName - FileUtils.saveBitmap(cpPath, resource) - val view = LayoutInflater.from(this@BackgroundChooser).inflate(R.layout.app_background_item, binding.listFather, false) - Glide.with(this@BackgroundChooser).load(cpPath).into(view.findViewById(R.id.background_image)) - val mTv = view.findViewById(R.id.background_name) - mTv.text = "在线背景" - view.setOnClickListener { - try { - val tgPath = BaseApplication.BACKGROUND_DOWNLOAD_FROM_URI_PATH + fileName - FileUtils.copyFile(cpPath, tgPath) - appViewModel.setAppBackground(false, tgPath) - }catch (e: Exception) { - e.printStackTrace() - makeToast("Setting error") - }finally { - finish() - } - } - binding.listFather.addView(view) - }catch (e: Exception) { - e.printStackTrace() - makeToast("Save bitmap failure") - } - } - } ) - } - }catch (e: Exception) { - onFailure(call, e) + val cachePath = "${cacheDir.absolutePath}/$fileName" + FileUtils.saveBitmap(cachePath, resource) + addRemoteBackgroundView(cachePath, fileName) + } catch (error: Exception) { + error.printStackTrace() + makeToast("Save bitmap failure") } } + }) + } + + private fun addRemoteBackgroundView(cachePath: String, fileName: String) { + val view = inflater.inflate(R.layout.app_background_item, binding.listFather, false) + val imageView = view.findViewById(R.id.background_image) + val nameView = view.findViewById(R.id.background_name) - override fun onFailure(call: Call, t: Throwable) { - t.printStackTrace() - makeToast("无网络连接") + Glide.with(this).load(cachePath).into(imageView) + nameView.text = getString(R.string.privacy_background_online_label) + + view.setOnClickListener { + try { + val targetDir = BaseApplication.BACKGROUND_DOWNLOAD_FROM_URI_PATH + if (!FileUtils.exists(targetDir)) { + FileUtils.makeRootDirectory(targetDir) } + val targetPath = targetDir + fileName + FileUtils.copyFile(cachePath, targetPath) + appViewModel.setAppBackground(false, targetPath) + } catch (error: Exception) { + error.printStackTrace() + makeToast("Setting error") + } finally { + finish() } - ) - } - }catch (e: Exception) { - e.printStackTrace() - makeToast("Load background failure") - finish() } + + binding.listFather.addView(view) } + + private fun extractFileName(uri: String): String { + val base = uri.substringAfterLast('/') + val clean = base.substringBefore('?').substringBefore('#') + return if (clean.isNotEmpty()) clean else "background_${System.currentTimeMillis()}.png" + } + + private data class LocalBackground( + val assetPath: String, + val isWeather: Boolean, + val displayName: String + ) } \ No newline at end of file diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/GuideActivity.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/GuideActivity.kt index 3ac72a1..231c6e7 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/GuideActivity.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/GuideActivity.kt @@ -9,6 +9,7 @@ import com.bumptech.glide.Glide import com.haodustudio.DailyNotes.BaseApplication import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.databinding.ActivityGuideBinding +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.helper.makeToast import com.haodustudio.DailyNotes.model.models.GuideImgList import com.haodustudio.DailyNotes.utils.NetworkUtils @@ -28,6 +29,12 @@ class GuideActivity : BaseActivity() { Glide.with(this).load(R.drawable.ic_cute_loading).into(binding.progressBar) + if (!PrivacySettingsManager.isCloudResourcesEnabled()) { + makeToast(getString(R.string.privacy_cloud_disabled_hint)) + finish() + return + } + NetworkUtils.isNetworkOnline(object : Handler() { override fun handleMessage(msg: Message) { super.handleMessage(msg) diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteChangeTemplate.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteChangeTemplate.kt index 0a9c149..95f1ed1 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteChangeTemplate.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteChangeTemplate.kt @@ -11,7 +11,9 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.haodustudio.DailyNotes.BaseApplication +import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.databinding.ActivityNoteChangeTemplateBinding +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.helper.makeToast import com.haodustudio.DailyNotes.helper.toGson import com.haodustudio.DailyNotes.model.listener.ChangeNoteDataCallBack @@ -54,12 +56,15 @@ class NoteChangeTemplate : BaseActivity(), View.OnClickListener { addTmpView(false, id) } - makeToast("获取在线模版") - val call = NetworkRepository.getTemplateListCall() - if (call == null) { - makeToast("无网络连接") + if (!PrivacySettingsManager.isCloudResourcesEnabled()) { + makeToast(getString(R.string.privacy_cloud_disabled_hint)) } else { - call.enqueue(object: Callback { + makeToast("获取在线模版") + val call = NetworkRepository.getTemplateListCall() + if (call == null) { + makeToast("无网络连接") + } else { + call.enqueue(object: Callback { override fun onResponse(call: Call, response: Response) { try { FileUtils.makeRootDirectory(BaseApplication.TEMPLATE_DOWNLOAD_FROM_URI_PATH) @@ -99,7 +104,8 @@ class NoteChangeTemplate : BaseActivity(), View.OnClickListener { makeToast("无网络连接") } - }) + }) + } } } diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteStickerChooser.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteStickerChooser.kt index b2030df..105a101 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteStickerChooser.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/NoteStickerChooser.kt @@ -8,7 +8,9 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.haodustudio.DailyNotes.BaseApplication +import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.databinding.ActivityNoteStickerChooserBinding +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.helper.makeToast import com.haodustudio.DailyNotes.model.models.StickerList import com.haodustudio.DailyNotes.utils.FileUtils @@ -47,12 +49,15 @@ class NoteStickerChooser : BaseActivity(noShot = true) { adapter = stickerAdapter } - makeToast("加载在线贴纸") - val call = NetworkRepository.getStickerListCall() - if (call == null) { - makeToast("无网络连接") + if (!PrivacySettingsManager.isCloudResourcesEnabled()) { + makeToast(getString(R.string.privacy_cloud_disabled_hint)) } else { - call.enqueue(object : Callback { + makeToast("加载在线贴纸") + val call = NetworkRepository.getStickerListCall() + if (call == null) { + makeToast("无网络连接") + } else { + call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { try { response.body().let { @@ -90,7 +95,8 @@ class NoteStickerChooser : BaseActivity(noShot = true) { makeToast("无网络连接") } - }) + }) + } } }catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/com/haodustudio/DailyNotes/view/activities/PrivacySettingsActivity.kt b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/PrivacySettingsActivity.kt new file mode 100644 index 0000000..5c2f067 --- /dev/null +++ b/app/src/main/java/com/haodustudio/DailyNotes/view/activities/PrivacySettingsActivity.kt @@ -0,0 +1,129 @@ +package com.haodustudio.DailyNotes.view.activities + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.StringRes +import com.haodustudio.DailyNotes.R +import com.haodustudio.DailyNotes.databinding.ActivityPrivacySettingsBinding +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager +import com.haodustudio.DailyNotes.helper.makeToast +import com.haodustudio.DailyNotes.view.activities.base.BaseActivity + +class PrivacySettingsActivity : BaseActivity() { + + private val binding by lazy { ActivityPrivacySettingsBinding.inflate(layoutInflater) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + setupListeners() + refreshIndicators() + } + + private fun setupListeners() { + binding.privacyCardCrash.setOnClickListener { + handleSettingClick(PrivacySetting.CRASH_REPORTING) + } + binding.privacyCardLocation.setOnClickListener { + handleSettingClick(PrivacySetting.LOCATION) + } + binding.privacyCardCloud.setOnClickListener { + handleSettingClick(PrivacySetting.CLOUD_RESOURCES) + } + } + + private fun handleSettingClick(setting: PrivacySetting) { + val current = isSettingEnabled(setting) + val target = !current + showConfirmationDialog(setting, target) + } + + private fun showConfirmationDialog(setting: PrivacySetting, targetState: Boolean) { + val dialog = Dialog(this) + dialog.setContentView(R.layout.dialog_privacy_confirmation) + dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + dialog.setCanceledOnTouchOutside(false) + + val titleView = dialog.findViewById(R.id.privacyDialogTitle) + val messageView = dialog.findViewById(R.id.privacyDialogMessage) + val cancelView = dialog.findViewById(R.id.privacyDialogCancel) + val confirmView = dialog.findViewById(R.id.privacyDialogConfirm) + + titleView.text = getString(R.string.privacy_confirm_title) + val actionText = getString( + if (targetState) R.string.privacy_confirm_enable else R.string.privacy_confirm_disable, + getString(setting.titleRes) + ) + messageView.text = buildString { + append(actionText) + append('\n') + append(getString(R.string.privacy_confirm_message)) + } + + cancelView.setOnClickListener { dialog.dismiss() } + confirmView.setOnClickListener { + applySetting(setting, targetState) + dialog.dismiss() + } + + dialog.show() + } + + private fun applySetting(setting: PrivacySetting, enabled: Boolean) { + when (setting) { + PrivacySetting.CRASH_REPORTING -> PrivacySettingsManager.updateCrashReportingEnabled(enabled) + PrivacySetting.LOCATION -> PrivacySettingsManager.updateLocationEnabled(enabled) + PrivacySetting.CLOUD_RESOURCES -> PrivacySettingsManager.updateCloudResourcesEnabled(enabled) + } + refreshIndicators() + val stateText = if (enabled) R.string.privacy_state_enabled else R.string.privacy_state_disabled + makeToast("${getString(setting.titleRes)}:${getString(stateText)}") + } + + private fun refreshIndicators() { + val crashEnabled = PrivacySettingsManager.isCrashReportingEnabled() + val locationEnabled = PrivacySettingsManager.isLocationEnabled() + val cloudEnabled = PrivacySettingsManager.isCloudResourcesEnabled() + + updateIndicator(binding.privacyCardCrashIndicator, crashEnabled) + updateIndicator(binding.privacyCardLocationIndicator, locationEnabled) + updateIndicator(binding.privacyCardCloudIndicator, cloudEnabled) + + binding.privacyCardCrashSubtitle.text = buildSubtitle(R.string.privacy_crash_description, crashEnabled) + binding.privacyCardLocationSubtitle.text = buildSubtitle(R.string.privacy_location_description, locationEnabled) + binding.privacyCardCloudSubtitle.text = buildSubtitle(R.string.privacy_cloud_description, cloudEnabled) + } + + private fun updateIndicator(indicator: ImageView, enabled: Boolean) { + indicator.setImageResource( + if (enabled) R.drawable.bg_privacy_indicator_on else R.drawable.bg_privacy_indicator_off + ) + } + + private fun isSettingEnabled(setting: PrivacySetting): Boolean = when (setting) { + PrivacySetting.CRASH_REPORTING -> PrivacySettingsManager.isCrashReportingEnabled() + PrivacySetting.LOCATION -> PrivacySettingsManager.isLocationEnabled() + PrivacySetting.CLOUD_RESOURCES -> PrivacySettingsManager.isCloudResourcesEnabled() + } + + private fun buildSubtitle(@StringRes descriptionRes: Int, enabled: Boolean): String { + val statusText = getString(if (enabled) R.string.privacy_state_enabled else R.string.privacy_state_disabled) + return buildString { + append(getString(descriptionRes)) + append('\n') + append(statusText) + } + } + + private enum class PrivacySetting(@StringRes val titleRes: Int) { + CRASH_REPORTING(R.string.privacy_crash_title), + LOCATION(R.string.privacy_location_title), + CLOUD_RESOURCES(R.string.privacy_cloud_title) + } +} diff --git a/app/src/main/java/com/haodustudio/DailyNotes/viewModel/viewModels/GlobalViewModel.kt b/app/src/main/java/com/haodustudio/DailyNotes/viewModel/viewModels/GlobalViewModel.kt index 2fb09e7..1e036a8 100644 --- a/app/src/main/java/com/haodustudio/DailyNotes/viewModel/viewModels/GlobalViewModel.kt +++ b/app/src/main/java/com/haodustudio/DailyNotes/viewModel/viewModels/GlobalViewModel.kt @@ -7,7 +7,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.haodustudio.DailyNotes.BaseApplication +import com.haodustudio.DailyNotes.R import com.haodustudio.DailyNotes.applicationScope +import com.haodustudio.DailyNotes.helper.PrivacySettingsManager import com.haodustudio.DailyNotes.helper.makeToast import com.haodustudio.DailyNotes.model.listener.AddNoteCallBack import com.haodustudio.DailyNotes.model.listener.ChangeNoteDataCallBack @@ -34,7 +36,7 @@ class GlobalViewModel : ViewModel() { init { appBackgroundPath.value = getAppBackgroundPath() - if (appConfigPre.getBoolean("app_background_is_weather", false)) { + if (appConfigPre.getBoolean("app_background_is_weather", false) && PrivacySettingsManager.isLocationEnabled()) { updateWeather(true) } } @@ -110,6 +112,10 @@ class GlobalViewModel : ViewModel() { } fun setAppBackground(isWeather: Boolean, path: String) { + if (isWeather && !PrivacySettingsManager.isLocationEnabled()) { + makeToast(BaseApplication.instance.getString(R.string.privacy_location_disabled_hint)) + return + } appConfigPre.edit { putBoolean("app_background_is_weather", isWeather) putString("app_background_path", path) @@ -128,6 +134,9 @@ class GlobalViewModel : ViewModel() { } private fun updateWeather(fromCache: Boolean = false) { + if (!PrivacySettingsManager.isLocationEnabled()) { + return + } if (System.currentTimeMillis() - appConfigPre.getLong("app_background_set_time", Long.MAX_VALUE) >= BaseApplication.WEATHER_REFRESH_TIME || !fromCache) { // refresh and post change val call = NetworkRepository.getWeatherCall() diff --git a/app/src/main/res/drawable/bg_privacy_back_button.xml b/app/src/main/res/drawable/bg_privacy_back_button.xml new file mode 100644 index 0000000..39cf3e0 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_back_button.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_banner.xml b/app/src/main/res/drawable/bg_privacy_banner.xml new file mode 100644 index 0000000..a7d5e9e --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_banner.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_card.xml b/app/src/main/res/drawable/bg_privacy_card.xml new file mode 100644 index 0000000..b91f056 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_card.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_dialog_button_cancel.xml b/app/src/main/res/drawable/bg_privacy_dialog_button_cancel.xml new file mode 100644 index 0000000..a3de336 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_dialog_button_cancel.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_dialog_button_confirm.xml b/app/src/main/res/drawable/bg_privacy_dialog_button_confirm.xml new file mode 100644 index 0000000..e07d2e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_dialog_button_confirm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_dialog_container.xml b/app/src/main/res/drawable/bg_privacy_dialog_container.xml new file mode 100644 index 0000000..8ba83a9 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_dialog_container.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_icon_container.xml b/app/src/main/res/drawable/bg_privacy_icon_container.xml new file mode 100644 index 0000000..a06a472 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_icon_container.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_indicator_off.xml b/app/src/main/res/drawable/bg_privacy_indicator_off.xml new file mode 100644 index 0000000..fc5b59d --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_indicator_off.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_privacy_indicator_on.xml b/app/src/main/res/drawable/bg_privacy_indicator_on.xml new file mode 100644 index 0000000..296a429 --- /dev/null +++ b/app/src/main/res/drawable/bg_privacy_indicator_on.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_privacy_cloud.xml b/app/src/main/res/drawable/ic_privacy_cloud.xml new file mode 100644 index 0000000..3c1f87d --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_cloud.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_privacy_crash.xml b/app/src/main/res/drawable/ic_privacy_crash.xml new file mode 100644 index 0000000..fb39eae --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_crash.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_privacy_dialog_user.xml b/app/src/main/res/drawable/ic_privacy_dialog_user.xml new file mode 100644 index 0000000..f982304 --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_dialog_user.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_privacy_location.xml b/app/src/main/res/drawable/ic_privacy_location.xml new file mode 100644 index 0000000..db46c8b --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_location.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/img.png b/app/src/main/res/drawable/img.png deleted file mode 100644 index 025d7b7..0000000 Binary files a/app/src/main/res/drawable/img.png and /dev/null differ diff --git a/app/src/main/res/drawable/privacy_banner.png b/app/src/main/res/drawable/privacy_banner.png new file mode 100644 index 0000000..d9f98c6 Binary files /dev/null and b/app/src/main/res/drawable/privacy_banner.png differ diff --git a/app/src/main/res/drawable/privacy_banner_placeholder.xml b/app/src/main/res/drawable/privacy_banner_placeholder.xml new file mode 100644 index 0000000..8914bee --- /dev/null +++ b/app/src/main/res/drawable/privacy_banner_placeholder.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/privacy_cloud_download.png b/app/src/main/res/drawable/privacy_cloud_download.png new file mode 100644 index 0000000..7360174 Binary files /dev/null and b/app/src/main/res/drawable/privacy_cloud_download.png differ diff --git a/app/src/main/res/drawable/privacy_dialog_close.png b/app/src/main/res/drawable/privacy_dialog_close.png new file mode 100644 index 0000000..7b7d16f Binary files /dev/null and b/app/src/main/res/drawable/privacy_dialog_close.png differ diff --git a/app/src/main/res/drawable/privacy_dialog_yes.png b/app/src/main/res/drawable/privacy_dialog_yes.png new file mode 100644 index 0000000..73489b5 Binary files /dev/null and b/app/src/main/res/drawable/privacy_dialog_yes.png differ diff --git a/app/src/main/res/drawable/privacy_location.png b/app/src/main/res/drawable/privacy_location.png new file mode 100644 index 0000000..d5667ac Binary files /dev/null and b/app/src/main/res/drawable/privacy_location.png differ diff --git a/app/src/main/res/layout/activity_privacy_settings.xml b/app/src/main/res/layout/activity_privacy_settings.xml new file mode 100644 index 0000000..de05cce --- /dev/null +++ b/app/src/main/res/layout/activity_privacy_settings.xml @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_privacy_confirmation.xml b/app/src/main/res/layout/dialog_privacy_confirmation.xml new file mode 100644 index 0000000..93e46f8 --- /dev/null +++ b/app/src/main/res/layout/dialog_privacy_confirmation.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 3bbde9b..eb68341 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,6 +32,31 @@ 无网络连接 敬请期待 + 返回 + 隐私设置 + 隐私设置宣传图 + PRIVACY + 我们十分重视用户的个人数据安全和隐私保护,因此我们提供了下列设置选项: + 崩溃数据上报 + 启用该选项后若程序崩溃将自动上报数据到Sentry + 位置信息 + 启用该选项后将允许上报位置信息以使用天气功能 + 云端资源获取 + 启用该选项后将允许从服务器获取在线资源 + 随天气变化 + 背景 %1$d + 在线背景 + 是否确认更新此项设置? + 更新后设置将在重启后立即生效 + 取消 + 确定 + 将启用“%1$s” + 将关闭“%1$s” + 已启用 + 已关闭 + 已关闭位置信息,无法使用随天气变化背景 + 已关闭云端资源获取 + 贡献者 核心开发成员 大版本开发者 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 19810b2..068c688 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -15,4 +15,54 @@ 2dp @drawable/shape_5dp_corners + + + + + + + + + + \ No newline at end of file