feat: logging to file

This commit is contained in:
rebelonion 2024-03-11 03:01:08 -05:00
parent 1028ac66cb
commit dbce7c5b29
67 changed files with 475 additions and 324 deletions

View file

@ -42,7 +42,7 @@ android {
buildTypes {
alpha {
applicationIdSuffix ".beta" // keep as beta by popular request
versionNameSuffix "-alpha02"
versionNameSuffix "-alpha03"
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"]
debuggable System.getenv("CI") == null
isDefault true

View file

@ -14,8 +14,18 @@ import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.*
import ani.dantotsu.BuildConfig
import ani.dantotsu.Mapper
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.currContext
import ani.dantotsu.logError
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString
import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@ -50,7 +60,7 @@ object AppUpdater {
res to res.substringAfter("# ").substringBefore("\n")
}
logger("Git Version : $version")
Logger.log("Git Version : $version")
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
CustomBottomDialog.newInstance().apply {

View file

@ -21,6 +21,8 @@ import ani.dantotsu.parsers.novel.NovelExtensionManager
import ani.dantotsu.settings.SettingsActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.FinalExceptionHandler
import ani.dantotsu.util.Logger
import com.google.android.material.color.DynamicColors
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
@ -83,7 +85,8 @@ class App : MultiDexApplication() {
}
crashlytics.setCustomKey("device Info", SettingsActivity.getDeviceInfo())
Logger.init(this)
Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler())
initializeNetwork(baseContext)
@ -99,19 +102,19 @@ class App : MultiDexApplication() {
val animeScope = CoroutineScope(Dispatchers.Default)
animeScope.launch {
animeExtensionManager.findAvailableExtensions()
logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
}
val mangaScope = CoroutineScope(Dispatchers.Default)
mangaScope.launch {
mangaExtensionManager.findAvailableExtensions()
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
}
val novelScope = CoroutineScope(Dispatchers.Default)
novelScope.launch {
novelExtensionManager.findAvailableExtensions()
logger("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
}
val commentsScope = CoroutineScope(Dispatchers.Default)
@ -138,7 +141,8 @@ class App : MultiDexApplication() {
try {
Notifications.createChannels(this)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
Logger.log("Failed to modify notification channels")
Logger.log(e)
}
}

View file

@ -28,7 +28,6 @@ import android.telephony.TelephonyManager
import android.text.InputFilter
import android.text.Spanned
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
@ -60,6 +59,7 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.subcriptions.NotificationClickReceiver
import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
@ -128,13 +128,6 @@ fun currActivity(): Activity? {
var loadMedia: Int? = null
var loadIsMAL = false
fun logger(e: Any?, print: Boolean = true) {
if (print)
//println(e)
Log.d("Logger", e.toString())
}
fun initActivity(a: Activity) {
val window = a.window
WindowCompat.setDecorFitsSystemWindows(window, false)
@ -306,7 +299,7 @@ class InputFilterMinMax(
val input = (dest.toString() + source.toString()).toDouble()
if (isInRange(min, max, input)) return null
} catch (nfe: NumberFormatException) {
logger(nfe.stackTraceToString())
Logger.log(nfe)
}
return ""
}
@ -761,7 +754,7 @@ fun saveImage(image: Bitmap, path: String, imageFileName: String): File? {
private fun scanFile(path: String, context: Context) {
MediaScannerConnection.scanFile(context, arrayOf(path), null) { p, _ ->
logger("Finished scanning $p")
Logger.log("Finished scanning $p")
}
}
@ -903,7 +896,7 @@ class EmptyAdapter(private val count: Int) : RecyclerView.Adapter<RecyclerView.V
fun toast(string: String?) {
if (string != null) {
logger(string)
Logger.log(string)
MainScope().launch {
Toast.makeText(currActivity()?.application ?: return@launch, string, Toast.LENGTH_SHORT)
.show()
@ -942,10 +935,10 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
}
return snackBar
}
logger(s)
Logger.log(s)
}
} catch (e: Exception) {
logger(e.stackTraceToString())
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
}
return null

View file

@ -49,6 +49,7 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.Logger
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import eu.kanade.domain.source.service.SourcePreferences

View file

@ -5,6 +5,7 @@ import android.os.Build
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.others.webview.CloudFlare
import ani.dantotsu.others.webview.WebViewBottomDialog
import ani.dantotsu.util.Logger
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser
import com.lagradost.nicehttp.addGenericDns
@ -104,6 +105,7 @@ fun logError(e: Throwable, post: Boolean = true, snackbar: Boolean = true) {
toast(e.localizedMessage)
}
e.printStackTrace()
Logger.log(e)
}
fun <T> tryWith(post: Boolean = false, snackbar: Boolean = true, call: () -> T): T? {

View file

@ -14,6 +14,7 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import java.util.Calendar
object Anilist {
@ -150,7 +151,7 @@ object Anilist {
cacheTime = cache ?: 10
)
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
Log.d("AnilistQuery", "Remaining requests: $remaining")
Logger.log("Remaining requests: $remaining")
if (json.code == 429) {
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0
@ -161,13 +162,13 @@ object Anilist {
toast("Rate limited. Try after $retry seconds")
throw Exception("Rate limited after $retry seconds")
}
if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down))
if (!json.text.startsWith("{")) {throw Exception(currContext()?.getString(R.string.anilist_down))}
if (show) println("Response : ${json.text}")
json.parsed()
} else null
} catch (e: Exception) {
if (show) snackString("Error fetching Anilist data: ${e.message}")
Log.e("AnilistQuery", "Error: ${e.message}")
Logger.log("Anilist Query Error: ${e.message}")
null
}
}

View file

@ -15,6 +15,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -99,6 +100,7 @@ class AnilistHomeViewModel : ViewModel() {
suspend fun initHomePage() {
val res = Anilist.query.initHomePage()
Logger.log("AnilistHomeViewModel : res=$res")
res["currentAnime"]?.let { animeContinue.postValue(it) }
res["favoriteAnime"]?.let { animeFav.postValue(it) }
res["plannedAnime"]?.let { animePlanned.postValue(it) }

View file

@ -4,7 +4,7 @@ import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.logError
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.startMainActivity
@ -16,7 +16,7 @@ class Login : AppCompatActivity() {
ThemeManager(this).applyTheme()
val data: Uri? = intent?.data
logger(data.toString())
Logger.log(data.toString())
try {
Anilist.token =
Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value

View file

@ -1,17 +1,18 @@
package ani.dantotsu.connections.crashlytics
import android.content.Context
import ani.dantotsu.util.Logger
class CrashlyticsStub : CrashlyticsInterface {
override fun initialize(context: Context) {
//no-op
}
override fun logException(e: Throwable) {
//no-op
Logger.log(e)
}
override fun log(message: String) {
//no-op
Logger.log(message)
}
override fun setUserId(id: String) {

View file

@ -15,7 +15,6 @@ import android.os.Environment
import android.os.IBinder
import android.os.PowerManager
import android.provider.MediaStore
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@ -26,6 +25,7 @@ import ani.dantotsu.connections.discord.serializers.User
import ani.dantotsu.isOnline
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
@ -274,7 +274,7 @@ class DiscordService : Service() {
return
}
}
t.message?.let { Log.d("WebSocket", "onFailure() $it") }
t.message?.let { Logger.log("onFailure() $it") }
log("WebSocket: Error, onFailure() reason: ${t.message}")
client = OkHttpClient()
client.newWebSocket(
@ -289,7 +289,7 @@ class DiscordService : Service() {
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
Log.d("WebSocket", "onClosing() $code $reason")
Logger.log("onClosing() $code $reason")
if (::heartbeatThread.isInitialized && !heartbeatThread.isInterrupted) {
heartbeatThread.interrupt()
}
@ -297,7 +297,7 @@ class DiscordService : Service() {
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
Log.d("WebSocket", "onClosed() $code $reason")
Logger.log("onClosed() $code $reason")
if (code >= 4000) {
log("WebSocket: Error, code: $code reason: $reason")
client = OkHttpClient()
@ -382,52 +382,7 @@ class DiscordService : Service() {
}
fun log(string: String) {
Log.d("WebSocket_Discord", string)
//log += "${SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().time)} $string\n"
}
fun saveLogToFile() {
val fileName = "log_${System.currentTimeMillis()}.txt"
// ContentValues to store file metadata
val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
}
}
// Inserting the file in the MediaStore
val resolver = baseContext.contentResolver
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
} else {
val directory =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(directory, fileName)
// Make sure the Downloads directory exists
if (!directory.exists()) {
directory.mkdirs()
}
// Use FileProvider to get the URI for the file
val authority =
"${baseContext.packageName}.provider" // Adjust with your app's package name
Uri.fromFile(file)
}
// Writing to the file
uri?.let {
resolver.openOutputStream(it).use { outputStream ->
OutputStreamWriter(outputStream).use { writer ->
writer.write(log)
}
}
} ?: run {
log("Error saving log file")
}
//Logger.log(string)
}
fun resume() {

View file

@ -27,7 +27,7 @@ import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.video.ExoplayerDownloadService
import ani.dantotsu.download.video.Helper
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.media.anime.AnimeWatchFragment
@ -249,7 +249,7 @@ class AnimeDownloaderService : Service() {
hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout
if (!downloadStarted) {
logger("Download failed to start")
Logger.log("Download failed to start")
builder.setContentText("${task.title} - ${task.episode} Download failed to start")
notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download failed to start")
@ -263,11 +263,11 @@ class AnimeDownloaderService : Service() {
val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
if (download != null) {
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) {
logger("Download failed")
Logger.log("Download failed")
builder.setContentText("${task.title} - ${task.episode} Download failed")
notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download failed")
logger("Download failed: ${download.failureReason}")
Logger.log("Download failed: ${download.failureReason}")
downloadsManager.removeDownload(
DownloadedType(
task.title,
@ -289,7 +289,7 @@ class AnimeDownloaderService : Service() {
break
}
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) {
logger("Download completed")
Logger.log("Download completed")
builder.setContentText("${task.title} - ${task.episode} Download completed")
notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download completed")
@ -309,7 +309,7 @@ class AnimeDownloaderService : Service() {
break
}
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) {
logger("Download stopped")
Logger.log("Download stopped")
builder.setContentText("${task.title} - ${task.episode} Download stopped")
notificationManager.notify(NOTIFICATION_ID, builder.build())
snackString("${task.title} - ${task.episode} Download stopped")
@ -328,7 +328,7 @@ class AnimeDownloaderService : Service() {
}
} catch (e: Exception) {
if (e.message?.contains("Coroutine was cancelled") == false) { //wut
logger("Exception while downloading file: ${e.message}")
Logger.log("Exception while downloading file: ${e.message}")
snackString("Exception while downloading file: ${e.message}")
e.printStackTrace()
Injekt.get<CrashlyticsInterface>().logException(e)

View file

@ -33,7 +33,7 @@ import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
@ -318,8 +318,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
val mediaJson = media.readText()
gson.fromJson(mediaJson, Media::class.java)
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
null
}
@ -374,8 +374,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
bannerUri
)
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineAnimeModel(
"unknown",

View file

@ -21,7 +21,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.manga.ImageData
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
@ -251,7 +251,7 @@ class MangaDownloaderService : Service() {
snackString("${task.title} - ${task.chapter} Download finished")
}
} catch (e: Exception) {
logger("Exception while downloading file: ${e.message}")
Logger.log("Exception while downloading file: ${e.message}")
snackString("Exception while downloading file: ${e.message}")
Injekt.get<CrashlyticsInterface>().logException(e)
broadcastDownloadFailed(task.chapter)

View file

@ -30,7 +30,7 @@ import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
@ -308,8 +308,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
val mediaJson = media.readText()
gson.fromJson(mediaJson, Media::class.java)
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
null
}
@ -358,8 +358,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
bannerUri
)
} catch (e: Exception) {
logger("Error loading media.json: ${e.message}")
logger(e.printStackTrace())
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineMangaModel(
"unknown",

View file

@ -20,7 +20,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.novel.NovelReadFragment
import ani.dantotsu.snackString
@ -186,15 +186,15 @@ class NovelDownloaderService : Service() {
val contentType = response.header("Content-Type")
val contentDisposition = response.header("Content-Disposition")
logger("Content-Type: $contentType")
logger("Content-Disposition: $contentDisposition")
Logger.log("Content-Type: $contentType")
Logger.log("Content-Disposition: $contentDisposition")
// Return true if the Content-Type or Content-Disposition indicates an EPUB file
contentType == "application/epub+zip" ||
(contentDisposition?.contains(".epub") == true)
}
} catch (e: Exception) {
logger("Error checking file type: ${e.message}")
Logger.log("Error checking file type: ${e.message}")
false
}
}
@ -225,12 +225,12 @@ class NovelDownloaderService : Service() {
if (!isEpubFile(task.downloadLink)) {
if (isAlreadyDownloaded(task.originalLink)) {
logger("Already downloaded")
Logger.log("Already downloaded")
broadcastDownloadFinished(task.originalLink)
snackString("Already downloaded")
return@withContext
}
logger("Download link is not an .epub file")
Logger.log("Download link is not an .epub file")
broadcastDownloadFailed(task.originalLink)
snackString("Download link is not an .epub file")
return@withContext
@ -301,7 +301,7 @@ class NovelDownloaderService : Service() {
withContext(Dispatchers.Main) {
val progress =
(downloadedBytes * 100 / totalBytes).toInt()
logger("Download progress: $progress")
Logger.log("Download progress: $progress")
broadcastDownloadProgress(task.originalLink, progress)
}
lastBroadcastUpdate = downloadedBytes
@ -316,7 +316,7 @@ class NovelDownloaderService : Service() {
}
}
} catch (e: Exception) {
logger("Exception while downloading .epub inside request: ${e.message}")
Logger.log("Exception while downloading .epub inside request: ${e.message}")
throw e
}
}
@ -340,7 +340,7 @@ class NovelDownloaderService : Service() {
snackString("${task.title} - ${task.chapter} Download finished")
}
} catch (e: Exception) {
logger("Exception while downloading .epub: ${e.message}")
Logger.log("Exception while downloading .epub: ${e.message}")
snackString("Exception while downloading .epub: ${e.message}")
Injekt.get<CrashlyticsInterface>().logException(e)
broadcastDownloadFailed(task.originalLink)

View file

@ -43,6 +43,7 @@ import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.parsers.Video
import ani.dantotsu.parsers.VideoType
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -157,15 +158,15 @@ object Helper {
finalException: Exception?
) {
if (download.state == Download.STATE_COMPLETED) {
Log.e("Downloader", "Download Completed")
Logger.log("Download Completed")
} else if (download.state == Download.STATE_FAILED) {
Log.e("Downloader", "Download Failed")
Logger.log("Download Failed")
} else if (download.state == Download.STATE_STOPPED) {
Log.e("Downloader", "Download Stopped")
Logger.log("Download Stopped")
} else if (download.state == Download.STATE_QUEUED) {
Log.e("Downloader", "Download Queued")
Logger.log("Download Queued")
} else if (download.state == Download.STATE_DOWNLOADING) {
Log.e("Downloader", "Download Downloading")
Logger.log("Download Downloading")
}
}
}

View file

@ -9,7 +9,7 @@ import androidx.lifecycle.ViewModel
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.anime.SelectorDialogFragment
import ani.dantotsu.media.manga.MangaChapter
@ -223,7 +223,7 @@ class MediaDetailsViewModel : ViewModel() {
}
fun setEpisode(ep: Episode?, who: String) {
logger("set episode ${ep?.number} - $who", false)
Logger.log("set episode ${ep?.number} - $who")
episode.postValue(ep)
MainScope().launch(Dispatchers.Main) {
episode.value = null
@ -270,7 +270,7 @@ class MediaDetailsViewModel : ViewModel() {
mangaChapters
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
logger("Loading Manga Chapters : $mangaLoaded")
Logger.log("Loading Manga Chapters : $mangaLoaded")
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
mangaLoaded[i] =
mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend

View file

@ -86,6 +86,7 @@ import ani.dantotsu.settings.PlayerSettingsActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
@ -1372,8 +1373,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
mediaItem = if (downloadedMediaItem == null) {
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
logger("url: ${video!!.file.url}")
logger("mimeType: $mimeType")
Logger.log("url: ${video!!.file.url}")
Logger.log("mimeType: $mimeType")
if (sub != null) {
val listofnotnullsubs = listOfNotNull(sub)

View file

@ -10,7 +10,7 @@ import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.LruCache
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.snackString
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
@ -32,8 +32,8 @@ data class ImageData(
try {
// Fetch the image
val response = httpSource.getImage(page)
logger("Response: ${response.code}")
logger("Response: ${response.message}")
Logger.log("Response: ${response.code}")
Logger.log("Response: ${response.message}")
// Convert the Response to an InputStream
val inputStream = response.body.byteStream()
@ -47,7 +47,7 @@ data class ImageData(
return@withContext bitmap
} catch (e: Exception) {
// Handle any exceptions
logger("An error occurred: ${e.message}")
Logger.log("An error occurred: ${e.message}")
snackString("An error occurred: ${e.message}")
return@withContext null
}

View file

@ -31,6 +31,7 @@ import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -59,7 +60,7 @@ class NovelReadFragment : Fragment(),
var loaded = false
override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) {
Log.e("downloadTrigger", novelDownloadPackage.link)
Logger.log("novel link: ${novelDownloadPackage.link}")
val downloadTask = NovelDownloaderService.DownloadTask(
title = media.mainName(),
chapter = novelDownloadPackage.novelName,

View file

@ -11,6 +11,7 @@ import ani.dantotsu.databinding.ItemNovelResponseBinding
import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.setAnimation
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
@ -181,7 +182,7 @@ class NovelResponseAdapter(
if (position != -1) {
list[position].extra?.remove("0")
list[position].extra?.set("0", "Downloading: $progress%")
Log.d("NovelResponseAdapter", "updateDownloadProgress: $progress, position: $position")
Logger.log( "updateDownloadProgress: $progress, position: $position")
notifyItemChanged(position)
}
}

View file

@ -1,6 +1,6 @@
package ani.dantotsu.others
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import java.util.regex.Pattern
import kotlin.math.pow
@ -64,7 +64,7 @@ class JsUnpacker(packedJS: String?) {
return decoded.toString()
}
} catch (e: Exception) {
logger(e)
Logger.log(e)
}
return null
}

View file

@ -2,7 +2,7 @@ package ani.dantotsu.others
import ani.dantotsu.FileUrl
import ani.dantotsu.client
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.tryWithSuspend
@ -41,8 +41,7 @@ object Kitsu {
}
suspend fun getKitsuEpisodesDetails(media: Media): Map<String, Episode>? {
val print = false
logger("Kitsu : title=${media.mainName()}", print)
Logger.log("Kitsu : title=${media.mainName()}")
val query =
"""
query {
@ -70,7 +69,7 @@ query {
val result = getKitsuData(query) ?: return null
logger("Kitsu : result=$result", print)
//Logger.log("Kitsu : result=$result")
media.idKitsu = result.data?.lookupMapping?.id
val a = (result.data?.lookupMapping?.episodes?.nodes ?: return null).mapNotNull { ep ->
val num = ep?.number?.toString() ?: return@mapNotNull null
@ -81,7 +80,7 @@ query {
thumb = FileUrl[ep.thumbnail?.original?.url],
)
}.toMap()
logger("Kitsu : a=$a", print)
//Logger.log("Kitsu : a=$a")
return a
}

View file

@ -11,7 +11,7 @@ import android.os.Environment
import android.provider.MediaStore
import ani.dantotsu.FileUrl
import ani.dantotsu.currContext
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.anime.AnimeNameAdapter
import ani.dantotsu.media.manga.ImageData
import ani.dantotsu.media.manga.MangaCache
@ -129,7 +129,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource
?: return false
currContext()?.let { context ->
logger("isDubAvailableSeparately: ${configurableSource.getPreferenceKey()}")
Logger.log("isDubAvailableSeparately: ${configurableSource.getPreferenceKey()}")
val sharedPreferences =
context.getSharedPreferences(
configurableSource.getPreferenceKey(),
@ -205,7 +205,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
}
return sortedEpisodes.map { SEpisodeToEpisode(it) }
} catch (e: Exception) {
logger("Exception: $e")
Logger.log("Exception: $e")
}
return emptyList()
}
@ -240,7 +240,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
val videos = source.getVideoList(sEpisode)
videos.map { VideoToVideoServer(it) }
} catch (e: Exception) {
logger("Exception occurred: ${e.message}")
Logger.log("Exception occurred: ${e.message}")
emptyList()
}
}
@ -260,16 +260,16 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
?: return emptyList())
return try {
val res = source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle()
logger("query: $query")
Logger.log("query: $query")
convertAnimesPageToShowResponse(res)
} catch (e: CloudflareBypassException) {
logger("Exception in search: $e")
Logger.log("Exception in search: $e")
withContext(Dispatchers.Main) {
snackString("Failed to bypass Cloudflare")
}
emptyList()
} catch (e: Exception) {
logger("General exception in search: $e")
Logger.log("General exception in search: $e")
emptyList()
}
}
@ -358,12 +358,12 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
val res = source.getChapterList(sManga)
val reversedRes = res.reversed()
val chapterList = reversedRes.map { SChapterToMangaChapter(it) }
logger("chapterList size: ${chapterList.size}")
logger("chapterList: ${chapterList[1].title}")
logger("chapterList: ${chapterList[1].description}")
Logger.log("chapterList size: ${chapterList.size}")
Logger.log("chapterList: ${chapterList[1].title}")
Logger.log("chapterList: ${chapterList[1].description}")
chapterList
} catch (e: Exception) {
logger("loadChapters Exception: $e")
Logger.log("loadChapters Exception: $e")
emptyList()
}
}
@ -379,7 +379,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
var imageDataList: List<ImageData> = listOf()
val ret = coroutineScope {
try {
logger("source.name " + source.name)
Logger.log("source.name " + source.name)
val res = source.getPageList(sChapter)
val reIndexedPages =
res.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
@ -388,7 +388,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
async(Dispatchers.IO) {
mangaCache.put(page.imageUrl ?: "", ImageData(page, source))
imageDataList += ImageData(page, source)
logger("put page: ${page.imageUrl}")
Logger.log("put page: ${page.imageUrl}")
pageToMangaImage(page)
}
}
@ -396,7 +396,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
deferreds.awaitAll()
} catch (e: Exception) {
logger("loadImages Exception: $e")
Logger.log("loadImages Exception: $e")
snackString("Failed to load images: $e")
emptyList()
}
@ -414,7 +414,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
return coroutineScope {
try {
logger("source.name " + source.name)
Logger.log("source.name " + source.name)
val res = source.getPageList(sChapter)
val reIndexedPages =
res.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
@ -430,7 +430,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
deferreds.awaitAll()
} catch (e: Exception) {
logger("loadImages Exception: $e")
Logger.log("loadImages Exception: $e")
snackString("Failed to load images: $e")
emptyList()
}
@ -446,8 +446,8 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
try {
// Fetch the image
val response = httpSource.getImage(page)
logger("Response: ${response.code}")
logger("Response: ${response.message}")
Logger.log("Response: ${response.code}")
Logger.log("Response: ${response.message}")
// Convert the Response to an InputStream
val inputStream = response.body.byteStream()
@ -467,7 +467,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
return@withContext bitmap
} catch (e: Exception) {
// Handle any exceptions
logger("An error occurred: ${e.message}")
Logger.log("An error occurred: ${e.message}")
return@withContext null
}
}
@ -500,7 +500,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
inputStream.close()
} catch (e: Exception) {
// Handle any exceptions
logger("An error occurred: ${e.message}")
Logger.log("An error occurred: ${e.message}")
}
}
}
@ -547,7 +547,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
}
} catch (e: Exception) {
// Handle exception here
logger("Exception while saving image: ${e.message}")
Logger.log("Exception while saving image: ${e.message}")
}
}
@ -562,16 +562,16 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
return try {
val res = source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
logger("res observable: $res")
Logger.log("res observable: $res")
convertMangasPageToShowResponse(res)
} catch (e: CloudflareBypassException) {
logger("Exception in search: $e")
Logger.log("Exception in search: $e")
withContext(Dispatchers.Main) {
snackString("Failed to bypass Cloudflare")
}
emptyList()
} catch (e: Exception) {
logger("General exception in search: $e")
Logger.log("General exception in search: $e")
emptyList()
}
}
@ -714,7 +714,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
// If the format is still undetermined, log an error
if (format == null) {
logger("Unknown video format: $videoUrl")
Logger.log("Unknown video format: $videoUrl")
format = VideoType.CONTAINER
}
val headersMap: Map<String, String> =
@ -746,7 +746,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
private fun headRequest(fileName: String, networkHelper: NetworkHelper): VideoType? {
return try {
logger("attempting head request for $fileName")
Logger.log("attempting head request for $fileName")
val request = Request.Builder()
.url(fileName)
.head()
@ -771,13 +771,13 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
else -> null
}
} else {
logger("failed head request for $fileName")
Logger.log("failed head request for $fileName")
null
}
}
} catch (e: Exception) {
logger("Exception in headRequest: $e")
Logger.log("Exception in headRequest: $e")
null
}

View file

@ -3,7 +3,7 @@ package ani.dantotsu.parsers
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager
import eu.kanade.tachiyomi.animesource.model.SAnime
@ -59,11 +59,11 @@ abstract class BaseParser {
saveShowResponse(mediaObj.id, response, true)
} else {
setUserText("Searching : ${mediaObj.mainName()}")
logger("Searching : ${mediaObj.mainName()}")
Logger.log("Searching : ${mediaObj.mainName()}")
val results = search(mediaObj.mainName())
//log all results
results.forEach {
logger("Result: ${it.name}")
Logger.log("Result: ${it.name}")
}
val sortedResults = if (results.isNotEmpty()) {
results.sortedByDescending {
@ -83,7 +83,7 @@ abstract class BaseParser {
) < 100
) {
setUserText("Searching : ${mediaObj.nameRomaji}")
logger("Searching : ${mediaObj.nameRomaji}")
Logger.log("Searching : ${mediaObj.nameRomaji}")
val romajiResults = search(mediaObj.nameRomaji)
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
romajiResults.sortedByDescending {
@ -96,10 +96,10 @@ abstract class BaseParser {
emptyList()
}
val closestRomaji = sortedRomajiResults.firstOrNull()
logger("Closest match from RomajiResults: ${closestRomaji?.name ?: "None"}")
Logger.log("Closest match from RomajiResults: ${closestRomaji?.name ?: "None"}")
response = if (response == null) {
logger("No exact match found in results. Using closest match from RomajiResults.")
Logger.log("No exact match found in results. Using closest match from RomajiResults.")
closestRomaji
} else {
val romajiRatio = FuzzySearch.ratio(
@ -110,14 +110,14 @@ abstract class BaseParser {
response.name.lowercase(),
mediaObj.mainName().lowercase()
)
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name.lowercase()}")
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name?.lowercase() ?: "None"}")
Logger.log("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name.lowercase()}")
Logger.log("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name?.lowercase() ?: "None"}")
if (romajiRatio > mainNameRatio) {
logger("RomajiResults has a closer match. Replacing response.")
Logger.log("RomajiResults has a closer match. Replacing response.")
closestRomaji
} else {
logger("Results has a closer or equal match. Keeping existing response.")
Logger.log("Results has a closer or equal match. Keeping existing response.")
response
}
}

View file

@ -1,7 +1,7 @@
package ani.dantotsu.parsers
import ani.dantotsu.Lazier
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.manga.MangaChapter
@ -96,7 +96,7 @@ abstract class MangaReadSources : BaseSources() {
}
//must be downloaded
if (show.sManga == null) {
logger("sManga is null")
Logger.log("sManga is null")
}
if (parser is OfflineMangaParser && show.sManga == null) {
tryWithSuspend(true) {
@ -106,11 +106,11 @@ abstract class MangaReadSources : BaseSources() {
}
}
} else {
logger("Parser is not an instance of OfflineMangaParser")
Logger.log("Parser is not an instance of OfflineMangaParser")
}
logger("map size ${map.size}")
Logger.log("map size ${map.size}")
return map
}
}

View file

@ -6,6 +6,7 @@ import ani.dantotsu.parsers.novel.DynamicNovelParser
import ani.dantotsu.parsers.novel.NovelExtension
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
@ -47,8 +48,8 @@ object NovelSources : NovelReadSources() {
}
private fun createParsersFromExtensions(extensions: List<NovelExtension.Installed>): List<Lazier<BaseParser>> {
Log.d("NovelSources", "createParsersFromExtensions")
Log.d("NovelSources", extensions.toString())
Logger.log("createParsersFromExtensions")
Logger.log(extensions.toString())
return extensions.map { extension ->
val name = extension.name
Lazier({ DynamicNovelParser(extension) }, name)

View file

@ -3,7 +3,7 @@ package ani.dantotsu.parsers
import android.os.Environment
import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.media.manga.MangaNameAdapter
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
@ -68,7 +68,7 @@ class OfflineMangaParser : MangaParser() {
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
}
for (image in images) {
logger("imageNumber: ${image.url.url}")
Logger.log("imageNumber: ${image.url.url}")
}
return images
}

View file

@ -1,6 +1,6 @@
package ani.dantotsu.parsers
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
class StringMatcher {
companion object {
@ -54,10 +54,10 @@ class StringMatcher {
val closestShowAndIndex = closestShow(target, shows)
val closestIndex = closestShowAndIndex.second
if (closestIndex == -1) {
logger("No closest show found for $target")
Logger.log("No closest show found for $target")
return shows // Return original list if no closest show found
}
logger("Closest show found for $target is ${closestShowAndIndex.first.name}")
Logger.log("Closest show found for $target is ${closestShowAndIndex.first.name}")
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(
closestIndex + 1,
shows.size

View file

@ -2,7 +2,7 @@ package ani.dantotsu.parsers.novel
import android.content.Context
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
@ -14,9 +14,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
@ -41,20 +39,20 @@ class NovelExtensionGithubApi {
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to get extensions from GitHub" }
Logger.log("Failed to get extensions from GitHub")
requiresFallbackSource = true
null
}
}
val response = githubResponse ?: run {
logger("using fallback source")
Logger.log("using fallback source")
networkService.client
.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json"))
.awaitSuccess()
}
logger("response: $response")
Logger.log("response: $response")
val extensions = with(json) {
response
@ -67,7 +65,7 @@ class NovelExtensionGithubApi {
/*if (extensions.size < 10) { //TODO: uncomment when more extensions are added
throw Exception()
}*/
logger("extensions: $extensions")
Logger.log("extensions: $extensions")
extensions
}
}

View file

@ -3,6 +3,7 @@ package ani.dantotsu.parsers.novel
import android.os.FileObserver
import android.util.Log
import ani.dantotsu.parsers.novel.FileObserver.fileObserver
import ani.dantotsu.util.Logger
import java.io.File
@ -22,24 +23,24 @@ class NovelExtensionFileObserver(private val listener: Listener, private val pat
override fun onEvent(event: Int, file: String?) {
Log.e("NovelExtensionFileObserver", "Event: $event")
Logger.log("Event: $event")
if (file == null) return
val fullPath = File(path, file)
when (event) {
CREATE -> {
Log.e("NovelExtensionFileObserver", "File created: $fullPath")
Logger.log("File created: $fullPath")
listener.onExtensionFileCreated(fullPath)
}
DELETE -> {
Log.e("NovelExtensionFileObserver", "File deleted: $fullPath")
Logger.log("File deleted: $fullPath")
listener.onExtensionFileDeleted(fullPath)
}
MODIFY -> {
Log.e("NovelExtensionFileObserver", "File modified: $fullPath")
Logger.log("File modified: $fullPath")
listener.onExtensionFileModified(fullPath)
}
}

View file

@ -15,13 +15,12 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.util.storage.getUriCompat
import logcat.LogPriority
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import tachiyomi.core.util.system.logcat
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@ -77,12 +76,12 @@ internal class NovelExtensionInstaller(private val context: Context) {
val fileToDelete = File("$sourcePath/${url.toUri().lastPathSegment}")
if (fileToDelete.exists()) {
if (fileToDelete.delete()) {
Log.i("Install APK", "APK file deleted successfully.")
Logger.log("APK file deleted successfully.")
} else {
Log.e("Install APK", "Failed to delete APK file.")
Logger.log("Failed to delete APK file.")
}
} else {
Log.e("Install APK", "APK file not found.")
Logger.log("APK file not found.")
}
// Register the receiver after removing (and unregistering) the previous download
@ -161,7 +160,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
// Check if source path is obtained correctly
if (sourcePath == null) {
Log.e("Install APK", "Source APK path not found.")
Logger.log("Source APK path not found.")
downloadsRelay.call(downloadId to InstallStep.Error)
return InstallStep.Error
}
@ -172,14 +171,14 @@ internal class NovelExtensionInstaller(private val context: Context) {
destinationDir.mkdirs()
}
if (destinationDir?.setWritable(true) == false) {
Log.e("Install APK", "Failed to set destinationDir to writable.")
Logger.log("Failed to set destinationDir to writable.")
downloadsRelay.call(downloadId to InstallStep.Error)
return InstallStep.Error
}
// Copy the file to the new location
copyFileToInternalStorage(sourcePath, destinationPath)
Log.i("Install APK", "APK moved to $destinationPath")
Logger.log("APK moved to $destinationPath")
downloadsRelay.call(downloadId to InstallStep.Installed)
return InstallStep.Installed
}
@ -198,9 +197,9 @@ internal class NovelExtensionInstaller(private val context: Context) {
val fileToDelete = File(apkPath)
//give write permission to the file
if (fileToDelete.exists() && !fileToDelete.canWrite()) {
Log.i("Uninstall APK", "File is not writable. Giving write permission.")
Logger.log("File is not writable. Giving write permission.")
val a = fileToDelete.setWritable(true)
Log.i("Uninstall APK", "Success: $a")
Logger.log("Success: $a")
}
//set the directory to writable
val destinationDir = File(apkPath).parentFile
@ -208,27 +207,27 @@ internal class NovelExtensionInstaller(private val context: Context) {
destinationDir.mkdirs()
}
val s = destinationDir?.setWritable(true)
Log.i("Uninstall APK", "Success destinationDir: $s")
Logger.log("Success destinationDir: $s")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.delete(fileToDelete.toPath())
} catch (e: Exception) {
Log.e("Uninstall APK", "Failed to delete APK file.")
Log.e("Uninstall APK", e.toString())
Logger.log("Failed to delete APK file.")
Logger.log(e)
snackString("Failed to delete APK file.")
}
} else {
if (fileToDelete.exists()) {
if (fileToDelete.delete()) {
Log.i("Uninstall APK", "APK file deleted successfully.")
Logger.log("APK file deleted successfully.")
snackString("APK file deleted successfully.")
} else {
Log.e("Uninstall APK", "Failed to delete APK file.")
Logger.log("Failed to delete APK file.")
snackString("Failed to delete APK file.")
}
} else {
Log.e("Uninstall APK", "APK file not found.")
Logger.log("APK file not found.")
snackString("APK file not found.")
}
}
@ -242,9 +241,9 @@ internal class NovelExtensionInstaller(private val context: Context) {
//delete the file if it already exists
if (destination.exists()) {
if (destination.delete()) {
Log.i("File Copy", "File deleted successfully.")
Logger.log("File deleted successfully.")
} else {
Log.e("File Copy", "Failed to delete file.")
Logger.log("Failed to delete file.")
}
}
@ -262,7 +261,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
outputChannel?.close()
}
Log.i("File Copy", "File copied to internal storage.")
Logger.log("File copied to internal storage.")
}
private fun getRealPathFromURI(context: Context, contentUri: Uri): String? {
@ -350,7 +349,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
// Set next installation step
if (uri == null) {
logcat(LogPriority.ERROR) { "Couldn't locate downloaded APK" }
Logger.log("Couldn't locate downloaded APK")
downloadsRelay.call(id to InstallStep.Error)
return
}
@ -371,7 +370,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
val uri = Uri.parse(localUri)
val path = uri.path
val pkgName = path?.substring(path.lastIndexOf('/') + 1)?.removeSuffix(".apk")
Log.i("Install APK", "Package name: $pkgName")
Logger.log("Package name: $pkgName")
return pkgName ?: ""
}
}

View file

@ -7,7 +7,7 @@ import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.os.Build
import android.util.Log
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.parsers.NovelInterface
import ani.dantotsu.snackString
import dalvik.system.PathClassLoader
@ -26,21 +26,20 @@ internal object NovelExtensionLoader {
val installDir = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/"
val results = mutableListOf<NovelLoadResult>()
//the number of files
Log.e("NovelExtensionLoader", "Loading extensions from $installDir")
Log.e(
"NovelExtensionLoader",
Logger.log("Loading extensions from $installDir")
Logger.log(
"Loading extensions from ${File(installDir).listFiles()?.size}"
)
File(installDir).setWritable(false)
File(installDir).listFiles()?.forEach {
//set the file to read only
it.setWritable(false)
Log.e("NovelExtensionLoader", "Loading extension ${it.name}")
Logger.log("Loading extension ${it.name}")
val extension = loadExtension(context, it)
if (extension is NovelLoadResult.Success) {
results.add(extension)
} else {
logger("Failed to load extension ${it.name}")
Logger.log("Failed to load extension ${it.name}")
}
}
return results
@ -62,7 +61,7 @@ internal object NovelExtensionLoader {
context.packageManager.getPackageArchiveInfo(path, 0)
} catch (error: Exception) {
// Unlikely, but the package may have been uninstalled at this point
logger("Failed to load extension $pkgName")
Logger.log("Failed to load extension $pkgName")
return NovelLoadResult.Error(Exception("Failed to load extension"))
}
return loadExtension(context, File(path))
@ -88,8 +87,8 @@ internal object NovelExtensionLoader {
val signatureHash = getSignatureHash(packageInfo)
if ((signatureHash == null) || !signatureHash.contains(officialSignature)) {
logger("Package ${packageInfo.packageName} isn't signed")
logger("signatureHash: $signatureHash")
Logger.log("Package ${packageInfo.packageName} isn't signed")
Logger.log("signatureHash: $signatureHash")
snackString("Package ${packageInfo.packageName} isn't signed")
//return NovelLoadResult.Error(Exception("Extension not signed"))
}
@ -128,12 +127,12 @@ internal object NovelExtensionLoader {
private fun loadSources(context: Context, file: File, className: String): List<NovelInterface> {
return try {
Log.e("NovelExtensionLoader", "isFileWritable: ${file.canWrite()}")
Logger.log("isFileWritable: ${file.canWrite()}")
if (file.canWrite()) {
val a = file.setWritable(false)
Log.e("NovelExtensionLoader", "success: $a")
Logger.log("success: $a")
}
Log.e("NovelExtensionLoader", "isFileWritable: ${file.canWrite()}")
Logger.log("isFileWritable: ${file.canWrite()}")
val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader)
val className =
"some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"

View file

@ -2,7 +2,7 @@ package ani.dantotsu.parsers.novel
import android.content.Context
import android.graphics.drawable.Drawable
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.snackString
import eu.kanade.tachiyomi.extension.InstallStep
import kotlinx.coroutines.flow.MutableStateFlow
@ -70,7 +70,7 @@ class NovelExtensionManager(private val context: Context) {
val extensions: List<NovelExtension.Available> = try {
api.findExtensions()
} catch (e: Exception) {
logger("Error finding extensions: ${e.message}")
Logger.log("Error finding extensions: ${e.message}")
withUIContext { snackString("Failed to get Novel extensions list") }
emptyList()
}

View file

@ -14,7 +14,7 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistQueries
import ani.dantotsu.connections.anilist.api.Activity
import ani.dantotsu.databinding.FragmentFeedBinding
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.snackString
import com.xwray.groupie.GroupieAdapter

View file

@ -25,7 +25,7 @@ import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
@ -146,7 +146,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
},
{ error ->
Injekt.get<CrashlyticsInterface>().logException(error)
Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
Logger.log(error) // Log the error
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR

View file

@ -32,6 +32,7 @@ import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.android.material.tabs.TabLayout
import com.google.android.material.textfield.TextInputLayout
import eu.kanade.tachiyomi.data.notification.Notifications
@ -143,7 +144,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
},
{ error ->
Injekt.get<CrashlyticsInterface>().logException(error)
Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
Logger.log(error) // Log the error
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR

View file

@ -30,6 +30,7 @@ import ani.dantotsu.parsers.novel.NovelExtensionManager
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.data.notification.Notifications
import kotlinx.coroutines.launch
import rx.android.schedulers.AndroidSchedulers
@ -72,7 +73,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler {
},
{ error ->
Injekt.get<CrashlyticsInterface>().logException(error)
Log.e("NovelExtensionsAdapter", "Error: ", error) // Log the error
Logger.log(error) // Log the error
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR
@ -216,7 +217,7 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension, parent, false)
Log.d("NovelExtensionsAdapter", "onCreateViewHolder: $view")
Logger.log("onCreateViewHolder: $view")
return ViewHolder(view)
}

View file

@ -59,7 +59,6 @@ class NovelExtensionsFragment : Fragment(),
lifecycleScope.launch {
viewModel.pagerFlow.collectLatest { pagingData ->
Log.d("NovelExtensionsFragment", "collectLatest")
adapter.submitData(pagingData)
}
}

View file

@ -45,7 +45,7 @@ import ani.dantotsu.download.video.ExoplayerDownloadService
import ani.dantotsu.downloadsPermission
import ani.dantotsu.initActivity
import ani.dantotsu.loadImage
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.navBarHeight
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.others.AppUpdater
@ -730,6 +730,16 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
binding.settingsShareUsername.isChecked = false
}
binding.settingsLogToFile.isChecked = PrefManager.getVal(PrefName.LogToFile)
binding.settingsLogToFile.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.LogToFile, isChecked)
restartApp()
}
binding.settingsShareLog.setOnClickListener {
Logger.shareLog(this)
}
binding.settingsAccountHelp.setOnClickListener {
val title = getString(R.string.account_help)
val full = getString(R.string.full_account_help)
@ -884,7 +894,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
if (dialogTag == "colorPicker") {
val color = extras.getInt(SimpleColorDialog.COLOR)
PrefManager.setVal(PrefName.CustomThemeInt, color)
logger("Custom Theme: $color")
Logger.log("Custom Theme: $color")
}
}
return true

View file

@ -61,11 +61,7 @@ class AnimeSourcePreferencesFragment : PreferenceFragmentCompat() {
pref.isIconSpaceReserved = false
if (pref is DialogPreference) {
pref.dialogTitle = pref.title
//println("pref.dialogTitle: ${pref.dialogTitle}") //TODO: could be useful for dub/sub selection
}
/*for (entry in sharedPreferences.all.entries) {
Log.d("Preferences", "Key: ${entry.key}, Value: ${entry.value}")
}*/
// Apply incognito IME for EditTextPreference
if (pref is EditTextPreference) {

View file

@ -159,6 +159,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
FirstComment(Pref(Location.Irrelevant, Boolean::class, true)),
CommentAuthResponse(Pref(Location.Irrelevant, AuthResponse::class, "")),
CommentTokenExpiry(Pref(Location.Irrelevant, Long::class, 0L)),
LogToFile(Pref(Location.Irrelevant, Boolean::class, false)),
//Protected
DiscordToken(Pref(Location.Protected, String::class, "")),

View file

@ -7,7 +7,7 @@ import android.content.Context
import android.content.Intent
import ani.dantotsu.currContext
import ani.dantotsu.isOnline
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
@ -22,7 +22,7 @@ class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_BOOT_COMPLETED -> tryWith(true) {
logger("Starting Dantotsu Subscription Service on Boot")
Logger.log("Starting Dantotsu Subscription Service on Boot")
context?.startSubscription()
}
}

View file

@ -10,6 +10,7 @@ import ani.dantotsu.parsers.Episode
import ani.dantotsu.parsers.MangaChapter
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -27,7 +28,7 @@ class Subscription {
alreadyStarted = true
SubscriptionWorker.enqueue(this)
AlarmReceiver.alarm(this)
} else logger("Already Subscribed")
} else Logger.log("Already Subscribed")
}
private var currentlyPerforming = false

View file

@ -0,0 +1,146 @@
package ani.dantotsu.util
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import ani.dantotsu.BuildConfig
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Date
import java.util.concurrent.Executors
object Logger {
var file: File? = null
private val loggerExecutor = Executors.newSingleThreadExecutor()
fun init(context: Context) {
try {
if (!PrefManager.getVal<Boolean>(PrefName.LogToFile) || file != null) return
file = File(context.getExternalFilesDir(null), "log.txt")
if (file?.exists() == true) {
val oldFile = File(context.getExternalFilesDir(null), "old_log.txt")
file?.copyTo(oldFile, true)
} else {
file?.createNewFile()
}
file?.writeText("log started\n")
file?.appendText("date/time: ${Date()}\n")
file?.appendText("device: ${android.os.Build.MODEL}\n")
file?.appendText("os version: ${android.os.Build.VERSION.RELEASE}\n")
file?.appendText(
"app version: ${
context.packageManager.getPackageInfo(
context.packageName,
0
).versionName
}\n"
)
file?.appendText(
"app version code: ${
context.packageManager.getPackageInfo(
context.packageName,
0
).versionCode
}\n"
)
file?.appendText("sdk version: ${android.os.Build.VERSION.SDK_INT}\n")
file?.appendText("manufacturer: ${android.os.Build.MANUFACTURER}\n")
file?.appendText("brand: ${android.os.Build.BRAND}\n")
file?.appendText("product: ${android.os.Build.PRODUCT}\n")
file?.appendText("device: ${android.os.Build.DEVICE}\n")
file?.appendText("hardware: ${android.os.Build.HARDWARE}\n")
file?.appendText("host: ${android.os.Build.HOST}\n")
file?.appendText("id: ${android.os.Build.ID}\n")
file?.appendText("type: ${android.os.Build.TYPE}\n")
file?.appendText("user: ${android.os.Build.USER}\n")
file?.appendText("tags: ${android.os.Build.TAGS}\n")
file?.appendText("time: ${android.os.Build.TIME}\n")
file?.appendText("radio: ${android.os.Build.RADIO}\n")
file?.appendText("bootloader: ${android.os.Build.BOOTLOADER}\n")
file?.appendText("board: ${android.os.Build.BOARD}\n")
file?.appendText("fingerprint: ${android.os.Build.FINGERPRINT}\n")
file?.appendText("supported_abis: ${android.os.Build.SUPPORTED_ABIS.joinToString()}\n")
file?.appendText("supported_32_bit_abis: ${android.os.Build.SUPPORTED_32_BIT_ABIS.joinToString()}\n")
file?.appendText("supported_64_bit_abis: ${android.os.Build.SUPPORTED_64_BIT_ABIS.joinToString()}\n")
file?.appendText("is emulator: ${android.os.Build.FINGERPRINT.contains("generic")}\n")
file?.appendText("--------------------------------\n")
} catch (e: Exception) {
Injekt.get<CrashlyticsInterface>().logException(e)
file = null
}
}
fun log(message: String) {
val trace = Thread.currentThread().stackTrace[3]
loggerExecutor.execute {
if (file == null) println(message)
else {
val className = trace.className
val methodName = trace.methodName
val lineNumber = trace.lineNumber
file?.appendText("date/time: ${Date()} | $className.$methodName($lineNumber)\n")
file?.appendText("message: $message\n-\n")
}
}
}
fun log(e: Exception) {
loggerExecutor.execute {
if (file == null) e.printStackTrace() else {
file?.appendText("---------------------------Exception---------------------------\n")
file?.appendText("date/time: ${Date()} | ${e.message}\n")
file?.appendText("trace: ${e.stackTrace}\n")
}
}
}
fun log(e: Throwable) {
loggerExecutor.execute {
if (file == null) e.printStackTrace() else {
file?.appendText("---------------------------Exception---------------------------\n")
file?.appendText("date/time: ${Date()} | ${e.message}\n")
file?.appendText("trace: ${e.stackTrace}\n")
}
}
}
fun shareLog(context: Context) {
if (file == null) {
snackString("No log file found")
return
}
val oldFile = File(context.getExternalFilesDir(null), "old_log.txt")
val fileToUse = if (oldFile.exists()) {
file?.readText()?.let { oldFile.appendText(it) }
oldFile
} else {
file
}
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(
Intent.EXTRA_STREAM,
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", fileToUse!!)
)
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Log file")
shareIntent.putExtra(Intent.EXTRA_TEXT, "Log file")
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(Intent.createChooser(shareIntent, "Share log file"))
}
}
class FinalExceptionHandler : Thread.UncaughtExceptionHandler {
private val defaultUEH: Thread.UncaughtExceptionHandler? =
Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) {
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
defaultUEH?.uncaughtException(t, e)
}
}

View file

@ -7,7 +7,7 @@ import android.graphics.BitmapFactory
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import ani.dantotsu.R
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
@ -19,7 +19,7 @@ class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: In
override fun onCreate() {
// 4 items for testing
widgetItems.clear()
logger("CurrentlyAiringRemoteViewsFactory onCreate")
Logger.log("CurrentlyAiringRemoteViewsFactory onCreate")
widgetItems = List(4) {
WidgetItem(
"Show $it",
@ -31,7 +31,7 @@ class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: In
override fun onDataSetChanged() {
// 4 items for testing
logger("CurrentlyAiringRemoteViewsFactory onDataSetChanged")
Logger.log("CurrentlyAiringRemoteViewsFactory onDataSetChanged")
widgetItems.clear()
widgetItems.add(
WidgetItem(
@ -79,7 +79,7 @@ class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: In
}
override fun getViewAt(position: Int): RemoteViews {
logger("CurrentlyAiringRemoteViewsFactory getViewAt")
Logger.log("CurrentlyAiringRemoteViewsFactory getViewAt")
val item = widgetItems[position]
val rv = RemoteViews(context.packageName, R.layout.item_currently_airing_widget).apply {
setTextViewText(R.id.text_show_title, item.title)

View file

@ -2,11 +2,11 @@ package ani.dantotsu.widgets
import android.content.Intent
import android.widget.RemoteViewsService
import ani.dantotsu.logger
import ani.dantotsu.util.Logger
class CurrentlyAiringRemoteViewsService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
logger("CurrentlyAiringRemoteViewsFactory onGetViewFactory")
Logger.log("CurrentlyAiringRemoteViewsFactory onGetViewFactory")
return CurrentlyAiringRemoteViewsFactory(applicationContext, intent)
}
}

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.anime
import android.content.Context
import android.graphics.drawable.Drawable
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
@ -16,11 +17,9 @@ import eu.kanade.tachiyomi.util.preference.plusAssign
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import logcat.LogPriority
import rx.Observable
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.source.anime.model.AnimeSourceData
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -120,7 +119,7 @@ class AnimeExtensionManager(
val extensions: List<AnimeExtension.Available> = try {
api.findExtensions()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Logger.log(e)
withUIContext { snackString("Failed to get extensions list") }
emptyList()
}

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.anime.api
import android.content.Context
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
@ -13,11 +14,9 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
@ -45,7 +44,7 @@ internal class AnimeExtensionGithubApi {
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to get extensions from GitHub" }
Logger.log("Failed to get extensions from GitHub")
requiresFallbackSource = true
null
}

View file

@ -10,12 +10,11 @@ import android.content.pm.PackageInstaller
import android.os.Build
import androidx.core.content.ContextCompat
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.util.lang.use
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.getUriSize
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
class PackageInstallerInstallerAnime(private val service: Service) : InstallerAnime(service) {
@ -30,7 +29,7 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
if (userAction == null) {
logcat(LogPriority.ERROR) { "Fatal error for $intent" }
Logger.log("Fatal error for $intent")
continueQueue(InstallStep.Error)
return
}
@ -89,11 +88,8 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
session.commit(intentSender)
}
} catch (e: Exception) {
logcat(
LogPriority.ERROR,
e
) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
logcat(LogPriority.ERROR) { "Exception: $e" }
Logger.log(e)
Logger.log("Failed to install extension ${entry.downloadId} ${entry.uri}")
snackString("Failed to install extension ${entry.downloadId} ${entry.uri}")
activeSession?.let { (_, sessionId) ->
packageInstaller.abandonSession(sessionId)

View file

@ -5,15 +5,14 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import logcat.LogPriority
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.system.logcat
/**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
@ -103,7 +102,7 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) :
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult {
val pkgName = getPackageNameFromIntent(intent)
if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" }
Logger.log("Package name not found")
return AnimeLoadResult.Error
}
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) {

View file

@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Build
import android.os.IBinder
import ani.dantotsu.R
import ani.dantotsu.util.Logger
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
@ -15,8 +16,6 @@ import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAn
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationBuilder
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
class AnimeExtensionInstallService : Service() {
@ -60,7 +59,7 @@ class AnimeExtensionInstallService : Service() {
)
else -> {
logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
Logger.log("Not implemented for installer $installerUsed")
stopSelf()
return START_NOT_STICKY
}

View file

@ -10,16 +10,15 @@ import android.os.Environment
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import ani.dantotsu.util.Logger
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.util.storage.getUriCompat
import logcat.LogPriority
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -248,7 +247,7 @@ internal class AnimeExtensionInstaller(private val context: Context) {
// Set next installation step
if (uri == null) {
logcat(LogPriority.ERROR) { "Couldn't locate downloaded APK" }
Logger.log("Couldn't locate downloaded APK")
downloadsRelay.call(id to InstallStep.Error)
return
}

View file

@ -6,6 +6,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.pm.PackageInfoCompat
import ani.dantotsu.util.Logger
import dalvik.system.PathClassLoader
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
@ -17,8 +18,6 @@ import eu.kanade.tachiyomi.util.lang.Hash
import eu.kanade.tachiyomi.util.system.getApplicationIcon
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
/**
@ -91,11 +90,11 @@ internal object AnimeExtensionLoader {
context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
logcat(LogPriority.ERROR, error)
Logger.log(error)
return AnimeLoadResult.Error
}
if (!isPackageAnExtension(pkgInfo)) {
logcat(LogPriority.WARN) { "Tried to load a package that wasn't a extension ($pkgName)" }
Logger.log("Tried to load a package that wasn't a extension ($pkgName)")
return AnimeLoadResult.Error
}
return loadExtension(context, pkgName, pkgInfo)
@ -119,7 +118,7 @@ internal object AnimeExtensionLoader {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
logcat(LogPriority.ERROR, error)
Logger.log(error)
return AnimeLoadResult.Error
}
@ -128,24 +127,23 @@ internal object AnimeExtensionLoader {
val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo)
if (versionName.isNullOrEmpty()) {
logcat(LogPriority.WARN) { "Missing versionName for extension $extName" }
Logger.log("Missing versionName for extension $extName")
return AnimeLoadResult.Error
}
// Validate lib version
val libVersion = versionName.substringBeforeLast('.').toDoubleOrNull()
if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
logcat(LogPriority.WARN) {
"Lib version is $libVersion, while only versions " +
Logger.log("Lib version is $libVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
}
)
return AnimeLoadResult.Error
}
val signatureHash = getSignatureHash(pkgInfo)
if (signatureHash == null) {
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
Logger.log("Package $pkgName isn't signed")
return AnimeLoadResult.Error
} else if (signatureHash !in trustedSignatures) {
val extension = AnimeExtension.Untrusted(
@ -156,13 +154,13 @@ internal object AnimeExtensionLoader {
libVersion,
signatureHash
)
logcat(LogPriority.WARN, message = { "Extension $pkgName isn't trusted" })
Logger.log("Extension $pkgName isn't trusted")
return AnimeLoadResult.Untrusted(extension)
}
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
if (!loadNsfwSource && isNsfw) {
logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" }
Logger.log("NSFW extension $pkgName not allowed")
return AnimeLoadResult.Error
}
@ -189,7 +187,7 @@ internal object AnimeExtensionLoader {
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" }
Logger.log("Extension load error: $extName ($it)")
return AnimeLoadResult.Error
}
}

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.manga
import android.content.Context
import android.graphics.drawable.Drawable
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi
@ -16,11 +17,9 @@ import eu.kanade.tachiyomi.util.preference.plusAssign
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import logcat.LogPriority
import rx.Observable
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.source.manga.model.MangaSourceData
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -117,7 +116,7 @@ class MangaExtensionManager(
val extensions: List<MangaExtension.Available> = try {
api.findExtensions()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Logger.log(e)
withUIContext { snackString("Failed to get manga extensions") }
emptyList()
}

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.manga.api
import android.content.Context
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources
@ -13,11 +14,9 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
@ -45,7 +44,7 @@ internal class MangaExtensionGithubApi {
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to get extensions from GitHub" }
Logger.log("Failed to get extensions from GitHub")
requiresFallbackSource = true
null
}

View file

@ -10,12 +10,11 @@ import android.content.pm.PackageInstaller
import android.os.Build
import androidx.core.content.ContextCompat
import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.util.lang.use
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.getUriSize
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
class PackageInstallerInstallerManga(private val service: Service) : InstallerManga(service) {
@ -30,7 +29,7 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
if (userAction == null) {
logcat(LogPriority.ERROR) { "Fatal error for $intent" }
Logger.log("Fatal error for $intent")
continueQueue(InstallStep.Error)
return
}
@ -89,8 +88,8 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
session.commit(intentSender)
}
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
logcat(LogPriority.ERROR) { "Exception: $e" }
Logger.log("Failed to install extension ${entry.downloadId} ${entry.uri}")
Logger.log(e)
snackString("Failed to install extension ${entry.downloadId} ${entry.uri}")
activeSession?.let { (_, sessionId) ->
packageInstaller.abandonSession(sessionId)

View file

@ -5,15 +5,14 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import logcat.LogPriority
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.system.logcat
/**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
@ -103,7 +102,7 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) :
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult {
val pkgName = getPackageNameFromIntent(intent)
if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" }
Logger.log("Package name not found")
return MangaLoadResult.Error
}
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) {

View file

@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Build
import android.os.IBinder
import ani.dantotsu.R
import ani.dantotsu.util.Logger
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga
@ -15,8 +16,6 @@ import eu.kanade.tachiyomi.extension.manga.installer.PackageInstallerInstallerMa
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationBuilder
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
class MangaExtensionInstallService : Service() {
@ -60,7 +59,7 @@ class MangaExtensionInstallService : Service() {
)
else -> {
logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
Logger.log("Not implemented for installer $installerUsed")
stopSelf()
return START_NOT_STICKY
}

View file

@ -10,16 +10,15 @@ import android.os.Environment
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import ani.dantotsu.util.Logger
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.util.storage.getUriCompat
import logcat.LogPriority
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -245,7 +244,7 @@ internal class MangaExtensionInstaller(private val context: Context) {
// Set next installation step
if (uri == null) {
logcat(LogPriority.ERROR) { "Couldn't locate downloaded APK" }
Logger.log("Couldn't locate downloaded APK")
downloadsRelay.call(id to InstallStep.Error)
return
}

View file

@ -6,6 +6,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.pm.PackageInfoCompat
import ani.dantotsu.util.Logger
import dalvik.system.PathClassLoader
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
@ -17,8 +18,6 @@ import eu.kanade.tachiyomi.util.lang.Hash
import eu.kanade.tachiyomi.util.system.getApplicationIcon
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
/**
@ -91,11 +90,11 @@ internal object MangaExtensionLoader {
context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
logcat(LogPriority.ERROR, error)
Logger.log(error)
return MangaLoadResult.Error
}
if (!isPackageAnExtension(pkgInfo)) {
logcat(LogPriority.WARN) { "Tried to load a package that wasn't a extension ($pkgName)" }
Logger.log("Tried to load a package that wasn't a extension ($pkgName)")
return MangaLoadResult.Error
}
return loadMangaExtension(context, pkgName, pkgInfo)
@ -119,7 +118,7 @@ internal object MangaExtensionLoader {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
logcat(LogPriority.ERROR, error)
Logger.log(error)
return MangaLoadResult.Error
}
@ -129,17 +128,16 @@ internal object MangaExtensionLoader {
val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo)
if (versionName.isNullOrEmpty()) {
logcat(LogPriority.WARN) { "Missing versionName for extension $extName" }
Logger.log("Missing versionName for extension $extName")
return MangaLoadResult.Error
}
// Validate lib version
val libVersion = versionName.substringBeforeLast('.').toDoubleOrNull()
if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
logcat(LogPriority.WARN) {
"Lib version is $libVersion, while only versions " +
Logger.log("Lib version is $libVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
}
)
return MangaLoadResult.Error
}
@ -147,18 +145,18 @@ internal object MangaExtensionLoader {
/* temporarily disabling signature check TODO: remove?
if (signatureHash == null) {
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
Logger.log("Package $pkgName isn't signed")
return MangaLoadResult.Error
} else if (signatureHash !in trustedSignatures) {
val extension = MangaExtension.Untrusted(extName, pkgName, versionName, versionCode, libVersion, signatureHash)
logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
Logger.log("Extension $pkgName isn't trusted")
return MangaLoadResult.Untrusted(extension)
}
*/
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
if (!loadNsfwSource && isNsfw) {
logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" }
Logger.log("NSFW extension $pkgName not allowed")
return MangaLoadResult.Error
}
@ -185,7 +183,7 @@ internal object MangaExtensionLoader {
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" }
Logger.log("Extension load error: $extName ($it)")
return MangaLoadResult.Error
}
}

View file

@ -21,10 +21,9 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.net.toUri
import ani.dantotsu.util.Logger
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.truncateCenter
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import java.io.File
import kotlin.math.roundToInt
@ -47,7 +46,7 @@ fun Context.copyToClipboard(label: String, content: String) {
toast("Copied to clipboard: " + content.truncateCenter(50))
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
Logger.log(e)
toast("Failed to copy to clipboard")
}
}

View file

@ -2,8 +2,7 @@ package eu.kanade.tachiyomi.util.system
import android.annotation.SuppressLint
import android.os.Build
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import ani.dantotsu.util.Logger
object DeviceUtil {
@ -72,7 +71,7 @@ object DeviceUtil {
.getDeclaredMethod("get", String::class.java)
.invoke(null, key) as String
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Unable to use SystemProperties.get()" }
Logger.log("Unable to use SystemProperties.get()")
null
}
}

View file

@ -6,8 +6,7 @@ import android.content.pm.PackageManager
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import ani.dantotsu.util.Logger
object WebViewUtil {
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
@ -20,7 +19,7 @@ object WebViewUtil {
// is not installed
CookieManager.getInstance()
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
Logger.log(e)
return false
}

View file

@ -989,6 +989,52 @@
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsLogToFile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_edit_note_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/log_to_file"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.58"
android:fontFamily="@font/poppins_bold"
android:text="@string/logging_warning" />
<ImageButton
android:id="@+id/settingsShareLog"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_share_24"
android:padding="16dp" />
</LinearLayout>
</ani.dantotsu.others.Xpandable>
<ani.dantotsu.others.Xpandable

View file

@ -692,5 +692,7 @@ Et magni quasi vel nemo omnis et voluptate quisquam vel corporis fuga ut consequ
Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exercitationem adipisci quo nemo aliquam ea numquam beatae. Eum Quis dolore aut quia accusantium sed vero autem vel quaerat eaque et beatae dicta non delectus galisum non ullam nulla.
</string>
<string name="log_to_file">Log to File</string>
<string name="logging_warning">Logging to a file will slow down the app. Only enable if you are experiencing issues.</string>
</resources>