diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt index 648d4ab7fb..d9b6fa0623 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt @@ -140,9 +140,9 @@ class MangaDetailsAdapter( fun showFloatingActionMode(view: TextView, content: String? = null, isTag: Boolean = false) fun showChapterFilter() fun favoriteManga(longPress: Boolean) - fun copyToClipboard(content: String, label: Int, useToast: Boolean = false) + fun copyContentToClipboard(content: String, label: Int, useToast: Boolean = false) fun customActionMode(view: TextView): ActionMode.Callback - fun copyToClipboard(content: String, label: String?, useToast: Boolean = false) + fun copyContentToClipboard(content: String, label: String?, useToast: Boolean = false) fun zoomImageFromThumb(thumbView: View) fun showTrackingSheet() fun updateScroll() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 58710ec595..c61fa2089a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -4,7 +4,6 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Activity import android.content.ClipData -import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.graphics.Color @@ -109,6 +108,7 @@ import eu.kanade.tachiyomi.util.system.setCustomTitleAndMessage import eu.kanade.tachiyomi.util.system.timeSpanFromNow import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.activityBinding +import eu.kanade.tachiyomi.util.view.copyToClipboard import eu.kanade.tachiyomi.util.view.findChild import eu.kanade.tachiyomi.util.view.getText import eu.kanade.tachiyomi.util.view.isControllerVisible @@ -1639,10 +1639,10 @@ class MangaDetailsController : * @param content the actual text to copy to the board * @param label Label to show to the user describing the content */ - override fun copyToClipboard(content: String, label: Int, useToast: Boolean) { + override fun copyContentToClipboard(content: String, label: Int, useToast: Boolean) { val view = view ?: return val contentType = if (label != 0) view.context.getString(label) else null - copyToClipboard(content, contentType, useToast) + copyContentToClipboard(content, contentType, useToast) } /** @@ -1651,22 +1651,8 @@ class MangaDetailsController : * @param content the actual text to copy to the board * @param label Label to show to the user describing the content */ - override fun copyToClipboard(content: String, label: String?, useToast: Boolean) { - if (content.isBlank()) return - - val activity = activity ?: return - val view = view ?: return - - val clipboard = activity.getSystemService(ClipboardManager::class.java) - clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) - - label ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return - if (useToast) { - activity.toast(view.context.getString(R.string._copied_to_clipboard, label)) - } else { - snack = view.snack(view.context.getString(R.string._copied_to_clipboard, label)) - } + override fun copyContentToClipboard(content: String, label: String?, useToast: Boolean) { + snack = copyToClipboard(content, label, useToast) } override fun showTrackingSheet() { @@ -1897,7 +1883,7 @@ class MangaDetailsController : item: MenuItem?, ): Boolean { when (item?.itemId) { - R.id.action_copy -> copyToClipboard(text, null) + R.id.action_copy -> copyContentToClipboard(text, null) R.id.action_source_search -> sourceSearch(text) R.id.action_global_search, R.id.action_local_search -> { if (authorText != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index d7086536e8..a6d45bb274 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -139,7 +139,7 @@ class MangaHeaderHolder( } title.setOnLongClickListener { title.text?.toString()?.toNormalized()?.let { - adapter.delegate.copyToClipboard(it, R.string.title) + adapter.delegate.copyContentToClipboard(it, R.string.title) } true } @@ -150,7 +150,7 @@ class MangaHeaderHolder( } mangaAuthor.setOnLongClickListener { mangaAuthor.text?.toString()?.let { - adapter.delegate.copyToClipboard(it, R.string.author) + adapter.delegate.copyContentToClipboard(it, R.string.author) } true } @@ -523,7 +523,7 @@ class MangaHeaderHolder( adapter.delegate.showFloatingActionMode(chip, isTag = true) } chip.setOnLongClickListener { - adapter.delegate.copyToClipboard(genreText, genreText) + adapter.delegate.copyContentToClipboard(genreText, genreText) true } this.addView(chip) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt index e8c5bb6293..c3583d8f01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt @@ -241,7 +241,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : override fun onTitleLongClick(position: Int) { val title = adapter?.getItem(position)?.track?.title ?: return - controller.copyToClipboard(title, R.string.title, true) + controller.copyContentToClipboard(title, R.string.title, true) } private fun startTransition(duration: Long = 100) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index d83acca506..ab9520bb9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -114,7 +114,7 @@ class AboutController : SettingsController() { preference { key = "pref_build_time" titleRes = R.string.build_time - summary = getFormattedBuildTime() + summary = getFormattedBuildTime(dateFormat) } preferenceCategory { @@ -234,15 +234,18 @@ class AboutController : SettingsController() { } } - private fun getFormattedBuildTime(): String { - try { - val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault()) - inputDf.timeZone = TimeZone.getTimeZone("UTC") - val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME + companion object { + fun getFormattedBuildTime(dateFormat: DateFormat): String { + try { + val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault()) + inputDf.timeZone = TimeZone.getTimeZone("UTC") + val buildTime = + inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME - return buildTime.toTimestampString(dateFormat) - } catch (e: ParseException) { - return BuildConfig.BUILD_TIME + return buildTime.toTimestampString(dateFormat) + } catch (e: ParseException) { + return BuildConfig.BUILD_TIME + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 6f678bf64e..34581a5dee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -40,6 +40,7 @@ import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.setting.database.ClearDatabaseController +import eu.kanade.tachiyomi.ui.setting.debug.DebugController import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.system.disableItems import eu.kanade.tachiyomi.util.system.isPackageInstalled @@ -107,35 +108,47 @@ class SettingsAdvancedController : SettingsController() { } } - val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager? - if (pm != null) { - preference { - key = "disable_batt_opt" - titleRes = R.string.disable_battery_optimization - summaryRes = R.string.disable_if_issues_with_updating + preference { + key = "debug_info" + titleRes = R.string.pref_debug_info - onClick { - val packageName: String = context.packageName - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - val intent = Intent().apply { - action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = "package:$packageName".toUri() - } - startActivity(intent) - } else { - context.toast(R.string.battery_optimization_disabled) - } - } + onClick { + router.pushController(DebugController().withFadeTransaction()) } } - preference { - key = "pref_dont_kill_my_app" - title = "Don't kill my app!" - summaryRes = R.string.about_dont_kill_my_app + preferenceCategory { + titleRes = R.string.label_background_activity + val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager? + if (pm != null) { + preference { + key = "disable_batt_opt" + titleRes = R.string.disable_battery_optimization + summaryRes = R.string.disable_if_issues_with_updating - onClick { - openInBrowser("https://dontkillmyapp.com/") + onClick { + val packageName: String = context.packageName + if (!pm.isIgnoringBatteryOptimizations(packageName)) { + val intent = Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = "package:$packageName".toUri() + } + startActivity(intent) + } else { + context.toast(R.string.battery_optimization_disabled) + } + } + } + } + + preference { + key = "pref_dont_kill_my_app" + title = "Don't kill my app!" + summaryRes = R.string.about_dont_kill_my_app + + onClick { + openInBrowser("https://dontkillmyapp.com/") + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/BackupSchemaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/BackupSchemaController.kt new file mode 100644 index 0000000000..c85166bdf5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/BackupSchemaController.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.ui.setting.debug + +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.backup.models.Backup +import eu.kanade.tachiyomi.databinding.SubDebugControllerBinding +import eu.kanade.tachiyomi.ui.base.controller.BaseController +import eu.kanade.tachiyomi.util.view.copyToClipboard +import eu.kanade.tachiyomi.util.view.scrollViewWith +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator + +class BackupSchemaController : BaseController() { + + companion object { + const val title = "Backup file schema" + } + + private val itemAdapter = ItemAdapter() + private val fastAdapter = FastAdapter.with(itemAdapter) + private val schema = ProtoBufSchemaGenerator.generateSchemaText(Backup.serializer().descriptor) + + override fun getTitle() = title + override fun createBinding(inflater: LayoutInflater) = + SubDebugControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + scrollViewWith(binding.recycler, padBottom = true) + fastAdapter.setHasStableIds(true) + binding.recycler.layoutManager = LinearLayoutManager(view.context) + binding.recycler.adapter = fastAdapter + itemAdapter.add(DebugInfoItem(schema, false)) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.sub_debug_info, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_copy -> copyToClipboard(schema, "Backup file schema", true) + } + return super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugController.kt new file mode 100644 index 0000000000..ec0db8a797 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugController.kt @@ -0,0 +1,100 @@ +package eu.kanade.tachiyomi.ui.setting.debug + +import android.os.Build +import androidx.preference.PreferenceScreen +import androidx.webkit.WebViewCompat +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.more.AboutController +import eu.kanade.tachiyomi.ui.setting.SettingsController +import eu.kanade.tachiyomi.ui.setting.onClick +import eu.kanade.tachiyomi.ui.setting.preference +import eu.kanade.tachiyomi.ui.setting.preferenceCategory +import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.view.withFadeTransaction +import java.text.DateFormat + +class DebugController : SettingsController() { + + override fun getTitle() = resources?.getString(R.string.pref_debug_info) + + private val dateFormat: DateFormat by lazy { + preferences.dateFormat() + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { + preference { + title = WorkerInfoController.title + onClick { + router.pushController(WorkerInfoController().withFadeTransaction()) + } + } + preference { + title = BackupSchemaController.title + onClick { + router.pushController(BackupSchemaController().withFadeTransaction()) + } + } + preferenceCategory { + title = "App Info" + preference { + key = "pref_version" + title = "Version" + summary = if (BuildConfig.DEBUG) { + "r" + BuildConfig.COMMIT_COUNT + } else { + BuildConfig.VERSION_NAME + } + } + preference { + key = "pref_build_time" + title = "Build Time" + summary = AboutController.getFormattedBuildTime(dateFormat) + } + preference { + key = "pref_webview_version" + title = "WebView version" + summary = getWebViewVersion() + } + } + + preferenceCategory { + title = "Device info" + preference { + title = "Model" + summary = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})" + } + if (DeviceUtil.oneUiVersion != null) { + preference { + title = "OneUI version" + summary = "${DeviceUtil.oneUiVersion}" + } + } else if (DeviceUtil.miuiMajorVersion != null) { + preference { + title = "MIUI version" + summary = "${DeviceUtil.miuiMajorVersion}" + } + } + val androidVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Build.VERSION.RELEASE_OR_CODENAME + } else { + Build.VERSION.RELEASE + } + preference { + title = "Android version" + summary = "$androidVersion (${Build.DISPLAY})" + } + } + } + + private fun getWebViewVersion(): String { + val activity = activity ?: return "Unknown" + val webView = + WebViewCompat.getCurrentWebViewPackage(activity) ?: return "how did you get here?" + val label = webView.applicationInfo.loadLabel(activity.packageManager) + val version = webView.versionName + return "$label $version" + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugInfoItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugInfoItem.kt new file mode 100644 index 0000000000..bd0f00546b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/DebugInfoItem.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.ui.setting.debug + +import android.view.View +import androidx.core.view.isVisible +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.databinding.DebugInfoItemBinding + +class DebugInfoItem(val text: String, val header: Boolean) : AbstractItem>() { + + /** defines the type defining this item. must be unique. preferably an id */ + override val type: Int = R.id.debug_title + + /** defines the layout which will be used for this item in the list */ + override val layoutRes: Int = R.layout.debug_info_item + + override var identifier = text.hashCode().toLong() + + override fun getViewHolder(v: View): FastAdapter.ViewHolder { + return ViewHolder(v) + } + + class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { + + val binding = DebugInfoItemBinding.bind(view) + + override fun bindView(item: DebugInfoItem, payloads: List) { + binding.debugTitle.isVisible = item.header + binding.debugSummary.isVisible = !item.header + if (item.header) { + binding.debugTitle.text = item.text + } else { + binding.debugSummary.text = item.text + } + } + + override fun unbindView(item: DebugInfoItem) { + binding.debugTitle.text = "" + binding.debugSummary.text = "" + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoController.kt new file mode 100644 index 0000000000..3af19136a0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoController.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.ui.setting.debug + +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.databinding.SubDebugControllerBinding +import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController +import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.view.copyToClipboard +import eu.kanade.tachiyomi.util.view.scrollViewWith +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.merge + +class WorkerInfoController : BaseCoroutineController() { + + companion object { + const val title = "Worker info" + } + + override var presenter = WorkerInfoPresenter() + + private val itemAdapter = ItemAdapter() + private val fastAdapter = FastAdapter.with(itemAdapter) + + override fun getTitle() = title + override fun createBinding(inflater: LayoutInflater) = + SubDebugControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + scrollViewWith(binding.recycler, padBottom = true) + + fastAdapter.setHasStableIds(true) + binding.recycler.layoutManager = LinearLayoutManager(view.context) + binding.recycler.adapter = fastAdapter + binding.recycler.itemAnimator = null + viewScope.launchUI { + merge(presenter.enqueued, presenter.finished, presenter.running).collectLatest { + itemAdapter.clear() + itemAdapter.add(DebugInfoItem("Enqueued", true)) + itemAdapter.add(DebugInfoItem(presenter.enqueued.value, false)) + itemAdapter.add(DebugInfoItem("Finished", true)) + itemAdapter.add(DebugInfoItem(presenter.finished.value, false)) + itemAdapter.add(DebugInfoItem("Running", true)) + itemAdapter.add(DebugInfoItem(presenter.running.value, false)) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.sub_debug_info, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_copy -> copyToClipboard( + "${presenter.enqueued.value}\n${presenter.finished.value}\n${presenter.running.value}", + "Backup file schema", + true, + ) + } + return super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoPresenter.kt new file mode 100644 index 0000000000..d4c5668f34 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/debug/WorkerInfoPresenter.kt @@ -0,0 +1,64 @@ +package eu.kanade.tachiyomi.ui.setting.debug + +import android.app.Application +import androidx.lifecycle.asFlow +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkQuery +import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class WorkerInfoPresenter : BaseCoroutinePresenter() { + private val workManager by lazy { WorkManager.getInstance(Injekt.get()) } + + val finished by lazy { + workManager + .getWorkInfosLiveData( + WorkQuery.fromStates( + WorkInfo.State.SUCCEEDED, + WorkInfo.State.FAILED, + WorkInfo.State.CANCELLED, + ), + ) + .asFlow() + .map(::constructString) + .stateIn(presenterScope, SharingStarted.WhileSubscribed(), "") + } + + val running by lazy { + workManager + .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING)) + .asFlow() + .map(::constructString) + .stateIn(presenterScope, SharingStarted.WhileSubscribed(), "") + } + + val enqueued by lazy { + workManager + .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED)) + .asFlow() + .map(::constructString) + .stateIn(presenterScope, SharingStarted.WhileSubscribed(), "") + } + + private fun constructString(list: List) = buildString { + if (list.isEmpty()) { + appendLine("-") + } else { + val newList = list.toList() + newList.forEach { workInfo -> + appendLine("Id: ${workInfo.id}") + appendLine("Tags:") + workInfo.tags.forEach { + appendLine(" - $it") + } + appendLine("State: ${workInfo.state}") + appendLine() + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt index be2eda4af6..b855b28545 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt @@ -10,6 +10,20 @@ object DeviceUtil { getSystemProperty("ro.miui.ui.version.name")?.isNotEmpty() ?: false } + /** + * Extracts the MIUI major version code from a string like "V12.5.3.0.QFGMIXM". + * + * @return MIUI major version code (e.g., 13) or null if can't be parsed. + */ + val miuiMajorVersion by lazy { + if (!isMiui) return@lazy null + + Build.VERSION.INCREMENTAL + .substringBefore('.') + .trimStart('V') + .toIntOrNull() + } + @SuppressLint("PrivateApi") fun isMiuiOptimizationDisabled(): Boolean { val sysProp = getSystemProperty("persist.sys.miui_optimization") @@ -30,6 +44,20 @@ object DeviceUtil { Build.MANUFACTURER.equals("samsung", ignoreCase = true) } + val oneUiVersion by lazy { + try { + val semPlatformIntField = Build.VERSION::class.java.getDeclaredField("SEM_PLATFORM_INT") + val version = semPlatformIntField.getInt(null) - 90000 + if (version < 0) { + 1.0 + } else { + ((version / 10000).toString() + "." + version % 10000 / 100).toDouble() + } + } catch (e: Exception) { + null + } + } + val invalidDefaultBrowsers = listOf( "android", "com.huawei.android.internal.app", diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index 9804b62f66..692d05a0fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -4,6 +4,8 @@ import android.Manifest import android.animation.Animator import android.animation.ValueAnimator import android.app.ActivityManager +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -50,6 +52,7 @@ import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.FadeChangeHandler +import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -824,6 +827,25 @@ fun Controller.openInBrowser(url: String) { } } +fun Controller.copyToClipboard(content: String, label: String?, useToast: Boolean): Snackbar? { + if (content.isBlank()) return null + + val activity = activity ?: return null + val view = view ?: return null + + val clipboard = activity.getSystemService(ClipboardManager::class.java) + clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) + + label ?: return null + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return null + return if (useToast) { + activity.toast(view.context.getString(R.string._copied_to_clipboard, label)) + null + } else { + view.snack(view.context.getString(R.string._copied_to_clipboard, label)) + } +} + val Controller.activityBinding: MainActivityBinding? get() = (activity as? MainActivity)?.binding diff --git a/app/src/main/res/layout/debug_info_item.xml b/app/src/main/res/layout/debug_info_item.xml new file mode 100644 index 0000000000..ae1de21579 --- /dev/null +++ b/app/src/main/res/layout/debug_info_item.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sub_debug_controller.xml b/app/src/main/res/layout/sub_debug_controller.xml new file mode 100644 index 0000000000..994004d4bf --- /dev/null +++ b/app/src/main/res/layout/sub_debug_controller.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/menu/sub_debug_info.xml b/app/src/main/res/menu/sub_debug_info.xml new file mode 100644 index 0000000000..e7b02fc46e --- /dev/null +++ b/app/src/main/res/menu/sub_debug_info.xml @@ -0,0 +1,9 @@ + + + + \ 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 f1ce85be7b..3f8fbb5c8d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -877,6 +877,8 @@ not in your library \nCurrently using: %1$s Most entries Select uninstalled sources + Debug info + Background activity Global search