Initial commit

This commit is contained in:
Finnley Somdahl 2023-10-17 18:42:43 -05:00
commit 21bfbfb139
520 changed files with 47819 additions and 0 deletions

View file

@ -0,0 +1,30 @@
package ani.dantotsu.settings
import java.io.Serializable
data class CurrentNovelReaderSettings(
var currentThemeName: String = "Default",
var layout: Layouts = Layouts.PAGED,
var dualPageMode: CurrentReaderSettings.DualPageModes = CurrentReaderSettings.DualPageModes.Automatic,
var lineHeight: Float = 1.4f,
var margin: Float = 0.06f,
var justify: Boolean = true,
var hyphenation: Boolean = true,
var useDarkTheme: Boolean = false,
var invert: Boolean = false,
var maxInlineSize: Int = 720,
var maxBlockSize: Int = 1440,
var horizontalScrollBar: Boolean = true,
var keepScreenOn: Boolean = false,
var volumeButtons: Boolean = false,
) : Serializable {
enum class Layouts(val string: String) {
PAGED("Paged"),
SCROLLED("Scrolled");
companion object {
operator fun get(value: Int) = values().firstOrNull { it.ordinal == value }
}
}
}

View file

@ -0,0 +1,63 @@
package ani.dantotsu.settings
import java.io.Serializable
data class CurrentReaderSettings(
var direction: Directions = Directions.TOP_TO_BOTTOM,
var layout: Layouts = Layouts.CONTINUOUS,
var dualPageMode: DualPageModes = DualPageModes.Automatic,
var overScrollMode: Boolean = true,
var trueColors: Boolean = false,
var rotation: Boolean = true,
var padding: Boolean = true,
var hidePageNumbers: Boolean = false,
var horizontalScrollBar: Boolean = true,
var keepScreenOn: Boolean = false,
var volumeButtons: Boolean = false,
var wrapImages: Boolean = false,
var longClickImage: Boolean = true,
var cropBorders: Boolean = false,
var cropBorderThreshold: Int = 10,
) : Serializable {
enum class Directions {
TOP_TO_BOTTOM,
RIGHT_TO_LEFT,
BOTTOM_TO_TOP,
LEFT_TO_RIGHT;
companion object {
operator fun get(value: Int) = values().firstOrNull { it.ordinal == value }
}
}
enum class Layouts {
PAGED,
CONTINUOUS_PAGED,
CONTINUOUS;
companion object {
operator fun get(value: Int) = values().firstOrNull { it.ordinal == value }
}
}
enum class DualPageModes {
No, Automatic, Force;
companion object {
operator fun get(value: Int) = values().firstOrNull { it.ordinal == value }
}
}
companion object {
fun applyWebtoon(settings: CurrentReaderSettings) {
settings.apply {
layout = Layouts.CONTINUOUS
direction = Directions.TOP_TO_BOTTOM
dualPageMode = DualPageModes.No
padding = false
}
}
}
}

View file

@ -0,0 +1,8 @@
package ani.dantotsu.settings
data class Developer(
val name: String,
val pfp: String,
val role: String,
val url: String
)

View file

@ -0,0 +1,38 @@
package ani.dantotsu.settings
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemDeveloperBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.setAnimation
class DevelopersAdapter(private val developers: Array<Developer>) :
RecyclerView.Adapter<DevelopersAdapter.DeveloperViewHolder>() {
private val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
inner class DeveloperViewHolder(val binding: ItemDeveloperBinding) : RecyclerView.ViewHolder(binding.root) {
init {
itemView.setOnClickListener {
openLinkInBrowser(developers[bindingAdapterPosition].url)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeveloperViewHolder {
return DeveloperViewHolder(ItemDeveloperBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: DeveloperViewHolder, position: Int) {
val b = holder.binding
setAnimation(b.root.context, b.root, uiSettings)
val dev = developers[position]
b.devName.text = dev.name
b.devProfile.loadImage(dev.pfp)
b.devRole.text = dev.role
}
override fun getItemCount(): Int = developers.size
}

View file

@ -0,0 +1,53 @@
package ani.dantotsu.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.databinding.BottomSheetDevelopersBinding
class DevelopersDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetDevelopersBinding? = null
private val binding get() = _binding!!
private val developers = arrayOf(
Developer("vorobyovgabriel","https://avatars.githubusercontent.com/u/99561687?s=120&v=4","Owner","https://github.com/vorobyovgabriel"),
Developer("brahmkshtriya","https://avatars.githubusercontent.com/u/69040506?s=120&v=4","Maintainer","https://github.com/brahmkshatriya"),
Developer("jeelpatel231","https://avatars.githubusercontent.com/u/33726155?s=120&v=4","Contributor","https://github.com/jeelpatel231"),
Developer("blatzar","https://avatars.githubusercontent.com/u/46196380?s=120&v=4","Contributor","https://github.com/Blatzar"),
Developer("bilibox","https://avatars.githubusercontent.com/u/1800580?s=120&v=4","Contributor","https://github.com/Bilibox"),
Developer("sutslec","https://avatars.githubusercontent.com/u/27722281?s=120&v=4","Contributor","https://github.com/Sutslec"),
Developer("4jx","https://avatars.githubusercontent.com/u/79868816?s=120&v=4","Contributor","https://github.com/4JX"),
Developer("xtrm-en","https://avatars.githubusercontent.com/u/26600206?s=120&v=4","Contributor","https://github.com/xtrm-en"),
Developer("scrazzz","https://avatars.githubusercontent.com/u/70033559?s=120&v=4","Contributor","https://github.com/scrazzz"),
Developer("defcoding","https://avatars.githubusercontent.com/u/39608887?s=120&v=4","Contributor","https://github.com/defcoding"),
Developer("adolar0042","https://avatars.githubusercontent.com/u/39769465?s=120&v=4","Contributor","https://github.com/adolar0042"),
Developer("diegopyl1209","https://avatars.githubusercontent.com/u/80992641?s=120&v=4","Contributor","https://github.com/diegopyl1209"),
Developer("sreekrishna2001","https://avatars.githubusercontent.com/u/67505103?s=120&v=4","Contributor","https://github.com/Sreekrishna2001"),
Developer("riimuru","https://avatars.githubusercontent.com/u/57333995?s=120&v=4","Contributor","https://github.com/riimuru"),
Developer("vu nguyen","https://avatars.githubusercontent.com/u/68330291?s=120&v=4","Contributor","https://github.com/hoangvu12"),
Developer("animejeff","https://avatars.githubusercontent.com/u/101831300?s=120&v=4","Contributor","https://github.com/AnimeJeff"),
Developer("antonydp","https://avatars.githubusercontent.com/u/38143733?s=120&v=4","Contributor","https://github.com/antonydp"),
Developer("tobybridle","https://avatars.githubusercontent.com/u/52335751?s=120&v=4","Contributor","https://github.com/TobyBridle"),
Developer("enimax","https://avatars.githubusercontent.com/u/107899019?s=120&v=4","Contributor","https://github.com/enimax-anime"),
Developer("vipulog","https://avatars.githubusercontent.com/u/90324465?s=120&v=4","Contributor","https://github.com/VipulOG")
)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = BottomSheetDevelopersBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.devsRecyclerView.adapter = DevelopersAdapter(developers)
binding.devsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}

View file

@ -0,0 +1,231 @@
package ani.dantotsu.settings
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build.*
import android.os.Build.VERSION.*
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
import ani.dantotsu.aniyomi.anime.AnimeExtensionManager
import ani.dantotsu.aniyomi.anime.model.AnimeExtension
import ani.dantotsu.databinding.ActivityExtensionsBinding
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy
class ExtensionsActivity : AppCompatActivity() {
private val restartMainActivity = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity)
}
lateinit var binding: ActivityExtensionsBinding
private lateinit var extensionsRecyclerView: RecyclerView
private lateinit var allextenstionsRecyclerView: RecyclerView
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
private val extensionsAdapter = ExtensionsAdapter { pkgName ->
animeExtensionManager.uninstallExtension(pkgName)
}
private val allExtensionsAdapter = AllExtensionsAdapter(lifecycleScope) { pkgName ->
if (SDK_INT >= VERSION_CODES.O) {
// If we don't have permission to install unknown apps, request it
if (!packageManager.canRequestPackageInstalls()) {
startActivityForResult(
Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
Uri.parse("package:$packageName")
), 1
)
}
}
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Start the installation process
animeExtensionManager.installExtension(pkgName)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ installStep ->
val builder = NotificationCompat.Builder(this,
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle("Installing extension")
.setContentText("Step: $installStep")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
},
{ error ->
val builder = NotificationCompat.Builder(this,
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle("Installation failed")
.setContentText("Error: ${error.message}")
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
},
{
val builder = NotificationCompat.Builder(this,
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS)
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
.setContentTitle("Installation complete")
.setContentText("The extension has been successfully installed.")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
}
)
}
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityExtensionsBinding.inflate(layoutInflater)
setContentView(binding.root)
extensionsRecyclerView = findViewById(R.id.extensionsRecyclerView)
extensionsRecyclerView.layoutManager = LinearLayoutManager(this)
extensionsRecyclerView.adapter = extensionsAdapter
allextenstionsRecyclerView = findViewById(R.id.allExtensionsRecyclerView)
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(this)
allextenstionsRecyclerView.adapter = allExtensionsAdapter
lifecycleScope.launch {
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
extensionsAdapter.updateData(extensions)
}
}
lifecycleScope.launch {
animeExtensionManager.availableExtensionsFlow.collect { extensions ->
allExtensionsAdapter.updateData(extensions)
}
}
initActivity(this)
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
onBackPressedDispatcher.addCallback(this, restartMainActivity)
binding.settingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
private class ExtensionsAdapter(private val onUninstallClicked: (String) -> Unit) : RecyclerView.Adapter<ExtensionsAdapter.ViewHolder>() {
private var extensions: List<AnimeExtension.Installed> = emptyList()
fun updateData(newExtensions: List<AnimeExtension.Installed>) {
extensions = newExtensions
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position]
holder.extensionNameTextView.text = extension.name
holder.extensionIconImageView.setImageDrawable(extension.icon)
holder.closeTextView.text = "Uninstall"
holder.closeTextView.setOnClickListener {
onUninstallClicked(extension.pkgName)
}
}
override fun getItemCount(): Int = extensions.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
}
}
private class AllExtensionsAdapter(private val coroutineScope: CoroutineScope,
private val onButtonClicked: (AnimeExtension.Available) -> Unit) : RecyclerView.Adapter<AllExtensionsAdapter.ViewHolder>() {
private var extensions: List<AnimeExtension.Available> = emptyList()
fun updateData(newExtensions: List<AnimeExtension.Available>) {
extensions = newExtensions
println("Extensions update: $extensions")
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllExtensionsAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension_all, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position]
holder.extensionNameTextView.text = extension.name
coroutineScope.launch {
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl)
holder.extensionIconImageView.setImageDrawable(drawable)
}
holder.closeTextView.text = "Install"
holder.closeTextView.setOnClickListener {
onButtonClicked(extension)
}
}
override fun getItemCount(): Int = extensions.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
}
suspend fun urlToDrawable(context: Context, url: String): Drawable? {
return withContext(Dispatchers.IO) {
try {
return@withContext Glide.with(context)
.load(url)
.submit()
.get()
} catch (e: Exception) {
e.printStackTrace()
return@withContext null
}
}
}
}
}

View file

@ -0,0 +1,49 @@
package ani.dantotsu.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityFaqBinding
import ani.dantotsu.initActivity
class FAQActivity : AppCompatActivity() {
private lateinit var binding: ActivityFaqBinding
private val faqs = listOf(
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_1), currContext()!!.getString(R.string.answer_1)),
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_2), currContext()!!.getString(R.string.answer_2)),
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_17), currContext()!!.getString(R.string.answer_17)),
Triple(R.drawable.ic_round_download_24, currContext()!!.getString(R.string.question_3), currContext()!!.getString(R.string.answer_3)),
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_16), currContext()!!.getString(R.string.answer_16)),
Triple(R.drawable.ic_round_dns_24, currContext()!!.getString(R.string.question_4), currContext()!!.getString(R.string.answer_4)),
Triple(R.drawable.ic_baseline_screen_lock_portrait_24, currContext()!!.getString(R.string.question_5), currContext()!!.getString(R.string.answer_5)),
Triple(R.drawable.ic_anilist, currContext()!!.getString(R.string.question_6), currContext()!!.getString(R.string.answer_6)),
Triple(R.drawable.ic_round_movie_filter_24, currContext()!!.getString(R.string.question_7), currContext()!!.getString(R.string.answer_7)),
Triple(R.drawable.ic_round_menu_book_24, currContext()!!.getString(R.string.question_8), currContext()!!.getString(R.string.answer_8)),
Triple(R.drawable.ic_round_lock_open_24, currContext()!!.getString(R.string.question_9), currContext()!!.getString(R.string.answer_9)),
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_10), currContext()!!.getString(R.string.answer_10)),
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_11), currContext()!!.getString(R.string.answer_11)),
Triple(R.drawable.ic_round_info_24, currContext()!!.getString(R.string.question_12), currContext()!!.getString(R.string.answer_12)),
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_13), currContext()!!.getString(R.string.answer_13)),
Triple(R.drawable.ic_round_art_track_24, currContext()!!.getString(R.string.question_14), currContext()!!.getString(R.string.answer_14)),
Triple(R.drawable.ic_round_video_settings_24, currContext()!!.getString(R.string.question_15), currContext()!!.getString(R.string.answer_15))
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFaqBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
binding.devsTitle2.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
binding.devsRecyclerView.adapter = FAQAdapter(faqs, supportFragmentManager)
binding.devsRecyclerView.layoutManager = LinearLayoutManager(this)
}
}

View file

@ -0,0 +1,45 @@
package ani.dantotsu.settings
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemQuestionBinding
import ani.dantotsu.loadData
import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.setAnimation
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
class FAQAdapter(private val questions: List<Triple<Int, String, String>>, private val manager: FragmentManager) :
RecyclerView.Adapter<FAQAdapter.FAQViewHolder>() {
private val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
inner class FAQViewHolder(val binding: ItemQuestionBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FAQViewHolder {
return FAQViewHolder(ItemQuestionBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: FAQViewHolder, position: Int) {
val b = holder.binding.root
setAnimation(b.context, b, uiSettings)
val faq = questions[position]
b.text = faq.second
b.setCompoundDrawablesWithIntrinsicBounds(faq.first, 0, 0, 0)
b.setOnClickListener {
CustomBottomDialog.newInstance().apply {
setTitleText(faq.second)
addView(
TextView(b.context).apply {
val markWon = Markwon.builder(b.context).usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
markWon.setMarkdown(this, faq.third)
}
)
}.show(manager, "dialog")
}
}
override fun getItemCount(): Int = questions.size
}

View file

@ -0,0 +1,36 @@
package ani.dantotsu.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetDevelopersBinding
class ForksDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetDevelopersBinding? = null
private val binding get() = _binding!!
private val developers = arrayOf(
Developer("Dantotsu","https://avatars.githubusercontent.com/u/87634197?v=4","rebelonion","https://github.com/rebelonion/Dantotsu"),
)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = BottomSheetDevelopersBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.devsTitle.setText(R.string.forks)
binding.devsRecyclerView.adapter = DevelopersAdapter(developers)
binding.devsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}

View file

@ -0,0 +1,10 @@
package ani.dantotsu.settings
import java.io.Serializable
data class NovelReaderSettings(
var showSource: Boolean = true,
var showSystemBars: Boolean = false,
var default: CurrentNovelReaderSettings = CurrentNovelReaderSettings(),
var askIndividual: Boolean = true,
) : Serializable

View file

@ -0,0 +1,49 @@
package ani.dantotsu.settings
import java.io.Serializable
data class PlayerSettings(
//Video
var videoInfo: Boolean = true,
var defaultSpeed: Int = 5,
var cursedSpeeds: Boolean = false,
var resize: Int = 0,
//Subtitles
var subtitles: Boolean = true,
var primaryColor: Int = 4,
var secondaryColor: Int = 0,
var outline: Int = 0,
var subBackground: Int = 0,
var subWindow: Int = 0,
var font: Int = 0,
var fontSize: Int = 20,
var locale: Int = 2,
//TimeStamps
var timeStampsEnabled: Boolean = true,
var useProxyForTimeStamps: Boolean = true,
var showTimeStampButton: Boolean = true,
//Auto
var autoSkipOPED: Boolean = false,
var autoPlay: Boolean = true,
var autoSkipFiller: Boolean = false,
//Update Progress
var askIndividual: Boolean = true,
var updateForH: Boolean = false,
var watchPercentage: Float = 0.8f,
//Behaviour
var alwaysContinue: Boolean = true,
var focusPause: Boolean = true,
var gestures: Boolean = true,
var doubleTap: Boolean = true,
var seekTime: Int = 10,
var skipTime: Int = 85,
//Other
var cast: Boolean = false,
var pip: Boolean = true
) : Serializable

View file

@ -0,0 +1,398 @@
package ani.dantotsu.settings
import android.app.AlertDialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import ani.dantotsu.*
import ani.dantotsu.databinding.ActivityPlayerSettingsBinding
import ani.dantotsu.media.Media
import ani.dantotsu.others.getSerialized
import ani.dantotsu.parsers.Subtitle
import com.google.android.material.snackbar.Snackbar
import kotlin.math.roundToInt
class PlayerSettingsActivity : AppCompatActivity() {
lateinit var binding: ActivityPlayerSettingsBinding
private val player = "player_settings"
var media:Media?=null
var subtitle:Subtitle?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPlayerSettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
onBackPressedDispatcher.addCallback(this) {
finish()
}
try {
media = intent.getSerialized("media")
subtitle = intent.getSerialized("subtitle")
} catch (e: Exception) {
toast(e.toString())
}
binding.playerSettingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
val settings = loadData<PlayerSettings>(player, toast = false) ?: PlayerSettings().apply { saveData(player, this) }
binding.playerSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
//Video
binding.playerSettingsVideoInfo.isChecked = settings.videoInfo
binding.playerSettingsVideoInfo.setOnCheckedChangeListener { _, isChecked ->
settings.videoInfo = isChecked
saveData(player, settings)
}
binding.playerSettingsQualityHeight.setText((loadData<Int>("maxHeight", toast = false) ?: 480).toString())
binding.playerSettingsQualityHeight.addTextChangedListener {
val height = binding.playerSettingsQualityHeight.text.toString().toIntOrNull()
saveData("maxHeight", height)
}
binding.playerSettingsQualityWidth.setText((loadData<Int>("maxWidth", toast = false) ?: 720).toString())
binding.playerSettingsQualityWidth.addTextChangedListener {
val height = binding.playerSettingsQualityWidth.text.toString().toIntOrNull()
saveData("maxWidth", height)
}
val speeds = arrayOf(0.25f, 0.33f, 0.5f, 0.66f, 0.75f, 1f, 1.25f, 1.33f, 1.5f, 1.66f, 1.75f, 2f)
val cursedSpeeds = arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f)
var curSpeedArr = if (settings.cursedSpeeds) cursedSpeeds else speeds
var speedsName = curSpeedArr.map { "${it}x" }.toTypedArray()
binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[settings.defaultSpeed])
val speedDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.default_speed))
binding.playerSettingsSpeed.setOnClickListener {
speedDialog.setSingleChoiceItems(speedsName, settings.defaultSpeed) { dialog, i ->
settings.defaultSpeed = i
binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[i])
saveData(player, settings)
dialog.dismiss()
}.show()
}
binding.playerSettingsCursedSpeeds.isChecked = settings.cursedSpeeds
binding.playerSettingsCursedSpeeds.setOnCheckedChangeListener { _, isChecked ->
settings.cursedSpeeds = isChecked
curSpeedArr = if (settings.cursedSpeeds) cursedSpeeds else speeds
settings.defaultSpeed = if (settings.cursedSpeeds) 0 else 5
speedsName = curSpeedArr.map { "${it}x" }.toTypedArray()
binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[settings.defaultSpeed])
saveData(player, settings)
}
//Time Stamp
binding.playerSettingsTimeStamps.isChecked = settings.timeStampsEnabled
binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked ->
settings.timeStampsEnabled = isChecked
saveData(player, settings)
}
binding.playerSettingsTimeStampsProxy.isChecked = settings.useProxyForTimeStamps
binding.playerSettingsTimeStampsProxy.setOnCheckedChangeListener { _, isChecked ->
settings.useProxyForTimeStamps = isChecked
saveData(player, settings)
}
binding.playerSettingsShowTimeStamp.isChecked = settings.showTimeStampButton
binding.playerSettingsShowTimeStamp.setOnCheckedChangeListener { _, isChecked ->
settings.showTimeStampButton = isChecked
saveData(player, settings)
}
//Auto
binding.playerSettingsAutoSkipOpEd.isChecked = settings.autoSkipOPED
binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked ->
settings.autoSkipOPED = isChecked
saveData(player, settings)
}
binding.playerSettingsAutoPlay.isChecked = settings.autoPlay
binding.playerSettingsAutoPlay.setOnCheckedChangeListener { _, isChecked ->
settings.autoPlay = isChecked
saveData(player, settings)
}
binding.playerSettingsAutoSkip.isChecked = settings.autoSkipFiller
binding.playerSettingsAutoSkip.setOnCheckedChangeListener { _, isChecked ->
settings.autoSkipFiller = isChecked
saveData(player, settings)
}
//Update Progress
binding.playerSettingsAskUpdateProgress.isChecked = settings.askIndividual
binding.playerSettingsAskUpdateProgress.setOnCheckedChangeListener { _, isChecked ->
settings.askIndividual = isChecked
saveData(player, settings)
}
binding.playerSettingsAskUpdateHentai.isChecked = settings.updateForH
binding.playerSettingsAskUpdateHentai.setOnCheckedChangeListener { _, isChecked ->
settings.updateForH = isChecked
if (isChecked) snackString(getString(R.string.very_bold))
saveData(player, settings)
}
binding.playerSettingsCompletePercentage.value = (settings.watchPercentage * 100).roundToInt().toFloat()
binding.playerSettingsCompletePercentage.addOnChangeListener { _, value, _ ->
settings.watchPercentage = value / 100
saveData(player, settings)
}
//Behaviour
binding.playerSettingsAlwaysContinue.isChecked = settings.alwaysContinue
binding.playerSettingsAlwaysContinue.setOnCheckedChangeListener { _, isChecked ->
settings.alwaysContinue = isChecked
saveData(player, settings)
}
binding.playerSettingsPauseVideo.isChecked = settings.focusPause
binding.playerSettingsPauseVideo.setOnCheckedChangeListener { _, isChecked ->
settings.focusPause = isChecked
saveData(player, settings)
}
binding.playerSettingsVerticalGestures.isChecked = settings.gestures
binding.playerSettingsVerticalGestures.setOnCheckedChangeListener { _, isChecked ->
settings.gestures = isChecked
saveData(player, settings)
}
binding.playerSettingsDoubleTap.isChecked = settings.doubleTap
binding.playerSettingsDoubleTap.setOnCheckedChangeListener { _, isChecked ->
settings.doubleTap = isChecked
saveData(player, settings)
}
binding.playerSettingsSeekTime.value = settings.seekTime.toFloat()
binding.playerSettingsSeekTime.addOnChangeListener { _, value, _ ->
settings.seekTime = value.toInt()
saveData(player, settings)
}
binding.exoSkipTime.setText(settings.skipTime.toString())
binding.exoSkipTime.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
binding.exoSkipTime.clearFocus()
}
false
}
binding.exoSkipTime.addTextChangedListener {
val time = binding.exoSkipTime.text.toString().toIntOrNull()
if (time != null) {
settings.skipTime = time
saveData(player, settings)
}
}
//Other
binding.playerSettingsPiP.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
visibility = View.VISIBLE
isChecked = settings.pip
setOnCheckedChangeListener { _, isChecked ->
settings.pip = isChecked
saveData(player, settings)
}
} else visibility = View.GONE
}
binding.playerSettingsCast.isChecked = settings.cast
binding.playerSettingsCast.setOnCheckedChangeListener { _, isChecked ->
settings.cast = isChecked
saveData(player, settings)
}
val resizeModes = arrayOf("Original", "Zoom", "Stretch")
val resizeDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.default_resize_mode))
binding.playerResizeMode.setOnClickListener {
resizeDialog.setSingleChoiceItems(resizeModes, settings.resize) { dialog, count ->
settings.resize = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
fun restartApp() {
Snackbar.make(
binding.root,
R.string.restart_app, Snackbar.LENGTH_SHORT
).apply {
val mainIntent =
Intent.makeRestartActivityTask(context.packageManager.getLaunchIntentForPackage(context.packageName)!!.component)
setAction("Do it!") {
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
show()
}
}
fun toggleButton(button: android.widget.Button, toggle: Boolean) {
button.isClickable = toggle
button.alpha = when (toggle) {
true -> 1f
false -> 0.5f
}
}
fun toggleSubOptions(isChecked: Boolean) {
toggleButton(binding.videoSubColorPrimary, isChecked)
toggleButton(binding.videoSubColorSecondary, isChecked)
toggleButton(binding.videoSubOutline, isChecked)
toggleButton(binding.videoSubFont, isChecked)
binding.subtitleFontSizeCard.isEnabled = isChecked
binding.subtitleFontSizeCard.isClickable = isChecked
binding.subtitleFontSizeCard.alpha = when (isChecked) {
true -> 1f
false -> 0.5f
}
binding.subtitleFontSize.isEnabled = isChecked
binding.subtitleFontSize.isClickable = isChecked
binding.subtitleFontSize.alpha = when (isChecked) {
true -> 1f
false -> 0.5f
}
ActivityPlayerSettingsBinding.bind(binding.root).subtitleFontSizeText.isEnabled = isChecked
ActivityPlayerSettingsBinding.bind(binding.root).subtitleFontSizeText.isClickable = isChecked
ActivityPlayerSettingsBinding.bind(binding.root).subtitleFontSizeText.alpha = when (isChecked) {
true -> 1f
false -> 0.5f
}
}
binding.subSwitch.isChecked = settings.subtitles
binding.subSwitch.setOnCheckedChangeListener { _, isChecked ->
settings.subtitles = isChecked
saveData(player, settings)
toggleSubOptions(isChecked)
restartApp()
}
val colorsPrimary =
arrayOf("Black", "Dark Gray", "Gray", "Light Gray", "White", "Red", "Yellow", "Green", "Cyan", "Blue", "Magenta")
val primaryColorDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.primary_sub_color))
binding.videoSubColorPrimary.setOnClickListener {
primaryColorDialog.setSingleChoiceItems(colorsPrimary, settings.primaryColor) { dialog, count ->
settings.primaryColor = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
val colorsSecondary = arrayOf(
"Black",
"Dark Gray",
"Gray",
"Light Gray",
"White",
"Red",
"Yellow",
"Green",
"Cyan",
"Blue",
"Magenta",
"Transparent"
)
val secondaryColorDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.outline_sub_color))
binding.videoSubColorSecondary.setOnClickListener {
secondaryColorDialog.setSingleChoiceItems(colorsSecondary, settings.secondaryColor) { dialog, count ->
settings.secondaryColor = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
val typesOutline = arrayOf("Outline", "Shine", "Drop Shadow", "None")
val outlineDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.outline_type))
binding.videoSubOutline.setOnClickListener {
outlineDialog.setSingleChoiceItems(typesOutline, settings.outline) { dialog, count ->
settings.outline = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
val colorsSubBackground = arrayOf(
"Transparent",
"Black",
"Dark Gray",
"Gray",
"Light Gray",
"White",
"Red",
"Yellow",
"Green",
"Cyan",
"Blue",
"Magenta"
)
val subBackgroundDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.outline_sub_color))
binding.videoSubColorBackground.setOnClickListener {
subBackgroundDialog.setSingleChoiceItems(colorsSubBackground, settings.subBackground) { dialog, count ->
settings.subBackground = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
val colorsSubWindow = arrayOf(
"Transparent",
"Black",
"Dark Gray",
"Gray",
"Light Gray",
"White",
"Red",
"Yellow",
"Green",
"Cyan",
"Blue",
"Magenta"
)
val subWindowDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.outline_sub_color))
binding.videoSubColorWindow.setOnClickListener {
subWindowDialog.setSingleChoiceItems(colorsSubWindow, settings.subWindow) { dialog, count ->
settings.subWindow = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
val fonts = arrayOf("Poppins Semi Bold", "Poppins Bold", "Poppins", "Poppins Thin")
val fontDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.subtitle_font))
binding.videoSubFont.setOnClickListener {
fontDialog.setSingleChoiceItems(fonts, settings.font) { dialog, count ->
settings.font = count
saveData(player, settings)
dialog.dismiss()
}.show()
}
binding.subtitleFontSize.setText(settings.fontSize.toString())
binding.subtitleFontSize.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
binding.subtitleFontSize.clearFocus()
}
false
}
binding.subtitleFontSize.addTextChangedListener {
val size = binding.subtitleFontSize.text.toString().toIntOrNull()
if (size != null) {
settings.fontSize = size
saveData(player, settings)
}
}
toggleSubOptions(settings.subtitles)
}
}

View file

@ -0,0 +1,14 @@
package ani.dantotsu.settings
import java.io.Serializable
data class ReaderSettings(
var showSource: Boolean = true,
var showSystemBars: Boolean = false,
var autoDetectWebtoon: Boolean = true,
var default: CurrentReaderSettings = CurrentReaderSettings(),
var askIndividual: Boolean = true,
var updateForH: Boolean = false
) : Serializable

View file

@ -0,0 +1,185 @@
package ani.dantotsu.settings
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import ani.dantotsu.R
import ani.dantotsu.databinding.ActivityReaderSettingsBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadData
import ani.dantotsu.navBarHeight
import ani.dantotsu.saveData
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
class ReaderSettingsActivity : AppCompatActivity() {
lateinit var binding: ActivityReaderSettingsBinding
private val reader = "reader_settings"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityReaderSettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
binding.readerSettingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
val settings = loadData<ReaderSettings>(reader, toast = false) ?: ReaderSettings().apply { saveData(reader, this) }
binding.readerSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
//General
binding.readerSettingsSourceName.isChecked = settings.showSource
binding.readerSettingsSourceName.setOnCheckedChangeListener { _, isChecked ->
settings.showSource = isChecked
saveData(reader, settings)
}
binding.readerSettingsSystemBars.isChecked = settings.showSystemBars
binding.readerSettingsSystemBars.setOnCheckedChangeListener { _, isChecked ->
settings.showSystemBars = isChecked
saveData(reader, settings)
}
binding.readerSettingsAutoWebToon.isChecked = settings.autoDetectWebtoon
binding.readerSettingsAutoWebToon.setOnCheckedChangeListener { _, isChecked ->
settings.autoDetectWebtoon = isChecked
saveData(reader, settings)
}
//Default
val layoutList = listOf(
binding.readerSettingsPaged,
binding.readerSettingsContinuousPaged,
binding.readerSettingsContinuous
)
binding.readerSettingsLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.default.layout.ordinal]
var selectedLayout = layoutList[settings.default.layout.ordinal]
selectedLayout.alpha = 1f
layoutList.forEachIndexed { index, imageButton ->
imageButton.setOnClickListener {
selectedLayout.alpha = 0.33f
selectedLayout = imageButton
selectedLayout.alpha = 1f
settings.default.layout = CurrentReaderSettings.Layouts[index]?:CurrentReaderSettings.Layouts.CONTINUOUS
binding.readerSettingsLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.default.layout.ordinal]
saveData(reader, settings)
}
}
binding.readerSettingsDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.default.direction.ordinal]
binding.readerSettingsDirection.rotation = 90f * (settings.default.direction.ordinal)
binding.readerSettingsDirection.setOnClickListener {
settings.default.direction = CurrentReaderSettings.Directions[settings.default.direction.ordinal + 1] ?: CurrentReaderSettings.Directions.TOP_TO_BOTTOM
binding.readerSettingsDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.default.direction.ordinal]
binding.readerSettingsDirection.rotation = 90f * (settings.default.direction.ordinal)
saveData(reader, settings)
}
val dualList = listOf(
binding.readerSettingsDualNo,
binding.readerSettingsDualAuto,
binding.readerSettingsDualForce
)
binding.readerSettingsDualPageText.text = settings.default.dualPageMode.toString()
var selectedDual = dualList[settings.default.dualPageMode.ordinal]
selectedDual.alpha = 1f
dualList.forEachIndexed { index, imageButton ->
imageButton.setOnClickListener {
selectedDual.alpha = 0.33f
selectedDual = imageButton
selectedDual.alpha = 1f
settings.default.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic
binding.readerSettingsDualPageText.text = settings.default.dualPageMode.toString()
saveData(reader, settings)
}
}
binding.readerSettingsTrueColors.isChecked = settings.default.trueColors
binding.readerSettingsTrueColors.setOnCheckedChangeListener { _, isChecked ->
settings.default.trueColors = isChecked
saveData(reader, settings)
}
binding.readerSettingsCropBorders.isChecked = settings.default.cropBorders
binding.readerSettingsCropBorders.setOnCheckedChangeListener { _, isChecked ->
settings.default.cropBorders = isChecked
saveData(reader, settings)
}
binding.readerSettingsImageRotation.isChecked = settings.default.rotation
binding.readerSettingsImageRotation.setOnCheckedChangeListener { _, isChecked ->
settings.default.rotation = isChecked
saveData(reader, settings)
}
binding.readerSettingsHorizontalScrollBar.isChecked = settings.default.horizontalScrollBar
binding.readerSettingsHorizontalScrollBar.setOnCheckedChangeListener { _, isChecked ->
settings.default.horizontalScrollBar = isChecked
saveData(reader, settings)
}
binding.readerSettingsPadding.isChecked = settings.default.padding
binding.readerSettingsPadding.setOnCheckedChangeListener { _, isChecked ->
settings.default.padding = isChecked
saveData(reader, settings)
}
binding.readerSettingsKeepScreenOn.isChecked = settings.default.keepScreenOn
binding.readerSettingsKeepScreenOn.setOnCheckedChangeListener { _, isChecked ->
settings.default.keepScreenOn = isChecked
saveData(reader, settings)
}
binding.readerSettingsHidePageNumbers.isChecked = settings.default.hidePageNumbers
binding.readerSettingsHidePageNumbers.setOnCheckedChangeListener { _, isChecked ->
settings.default.hidePageNumbers = isChecked
saveData(reader, settings)
}
binding.readerSettingsOverscroll.isChecked = settings.default.overScrollMode
binding.readerSettingsOverscroll.setOnCheckedChangeListener { _,isChecked ->
settings.default.overScrollMode = isChecked
saveData(reader, settings)
}
binding.readerSettingsVolumeButton.isChecked = settings.default.volumeButtons
binding.readerSettingsVolumeButton.setOnCheckedChangeListener { _,isChecked ->
settings.default.volumeButtons = isChecked
saveData(reader, settings)
}
binding.readerSettingsWrapImages.isChecked = settings.default.wrapImages
binding.readerSettingsWrapImages.setOnCheckedChangeListener { _,isChecked ->
settings.default.wrapImages = isChecked
saveData(reader, settings)
}
binding.readerSettingsLongClickImage.isChecked = settings.default.longClickImage
binding.readerSettingsLongClickImage.setOnCheckedChangeListener { _,isChecked ->
settings.default.longClickImage = isChecked
saveData(reader, settings)
}
//Update Progress
binding.readerSettingsAskUpdateProgress.isChecked = settings.askIndividual
binding.readerSettingsAskUpdateProgress.setOnCheckedChangeListener { _, isChecked ->
settings.askIndividual = isChecked
saveData(reader, settings)
}
binding.readerSettingsAskUpdateDoujins.isChecked = settings.updateForH
binding.readerSettingsAskUpdateDoujins.setOnCheckedChangeListener { _, isChecked ->
settings.updateForH = isChecked
if (isChecked) snackString(getString(R.string.very_bold))
saveData(reader, settings)
}
}
}

View file

@ -0,0 +1,519 @@
package ani.dantotsu.settings
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.graphics.drawable.Animatable
import android.os.Build.*
import android.os.Build.VERSION.*
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.*
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.discord.Discord
import ani.dantotsu.connections.mal.MAL
import ani.dantotsu.databinding.ActivitySettingsBinding
import ani.dantotsu.others.AppUpdater
import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.subcriptions.Notifications
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.random.Random
class SettingsActivity : AppCompatActivity() {
private val restartMainActivity = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() = startMainActivity(this@SettingsActivity)
}
lateinit var binding: ActivitySettingsBinding
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
binding.settingsVersion.text = getString(R.string.version_current, BuildConfig.VERSION_NAME)
binding.settingsVersion.setOnLongClickListener {
fun getArch(): String {
SUPPORTED_ABIS.forEach {
when (it) {
"arm64-v8a" -> return "aarch64"
"armeabi-v7a" -> return "arm"
"x86_64" -> return "x86_64"
"x86" -> return "i686"
}
}
return System.getProperty("os.arch") ?: System.getProperty("os.product.cpu.abi") ?: "Unknown Architecture"
}
val info = """
dantotsu Version: ${BuildConfig.VERSION_NAME}
Device: $BRAND $DEVICE
Architecture: ${getArch()}
OS Version: $CODENAME $RELEASE ($SDK_INT)
""".trimIndent()
copyToClipboard(info, false)
toast(getString(R.string.copied_device_info))
return@setOnLongClickListener true
}
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
onBackPressedDispatcher.addCallback(this, restartMainActivity)
binding.settingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
val animeSource = loadData<Int>("settings_def_anime_source")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0
if (MangaSources.names.isNotEmpty() && animeSource in 0 until MangaSources.names.size) {
binding.mangaSource.setText(MangaSources.names[animeSource], false)
}
binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names))
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
saveData("settings_def_anime_source", i)
binding.animeSource.clearFocus()
}
binding.settingsPlayer.setOnClickListener {
startActivity(Intent(this, PlayerSettingsActivity::class.java))
}
val managers = arrayOf("Default", "1DM", "ADM")
val downloadManagerDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle("Download Manager")
var downloadManager = loadData<Int>("settings_download_manager") ?: 0
binding.settingsDownloadManager.setOnClickListener {
downloadManagerDialog.setSingleChoiceItems(managers, downloadManager) { dialog, count ->
downloadManager = count
saveData("settings_download_manager", downloadManager)
dialog.dismiss()
}.show()
}
binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false
binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
val arrayOfFiles = ContextCompat.getExternalFilesDirs(this, null)
if (arrayOfFiles.size > 1 && arrayOfFiles[1] != null) {
saveData("sd_dl", true)
} else {
binding.settingsDownloadInSd.isChecked = false
saveData("sd_dl", false)
snackString(getString(R.string.noSdFound))
}
} else saveData("sd_dl", false)
}
binding.settingsContinueMedia.isChecked = loadData("continue_media") ?: true
binding.settingsContinueMedia.setOnCheckedChangeListener { _, isChecked ->
saveData("continue_media", isChecked)
}
binding.settingsRecentlyListOnly.isChecked = loadData("recently_list_only") ?: false
binding.settingsRecentlyListOnly.setOnCheckedChangeListener { _, isChecked ->
saveData("recently_list_only", isChecked)
}
val dns = listOf("None", "Google", "Cloudflare", "AdGuard")
binding.settingsDns.setText(dns[loadData("settings_dns") ?: 0], false)
binding.settingsDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, dns))
binding.settingsDns.setOnItemClickListener { _, _, i, _ ->
saveData("settings_dns", i)
initializeNetwork(this)
binding.settingsDns.clearFocus()
}
binding.settingsPreferDub.isChecked = loadData("settings_prefer_dub") ?: false
binding.settingsPreferDub.setOnCheckedChangeListener { _, isChecked ->
saveData("settings_prefer_dub", isChecked)
}
val mangaSource = loadData<Int>("settings_def_manga_source")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0
if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) {
binding.mangaSource.setText(MangaSources.names[mangaSource], false)
}
binding.mangaSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, MangaSources.names))
binding.mangaSource.setOnItemClickListener { _, _, i, _ ->
saveData("settings_def_manga_source", i)
binding.mangaSource.clearFocus()
}
binding.settingsReader.setOnClickListener {
startActivity(Intent(this, ReaderSettingsActivity::class.java))
}
val uiSettings: UserInterfaceSettings =
loadData("ui_settings", toast = false) ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
var previous: View = when (uiSettings.darkMode) {
null -> binding.settingsUiAuto
true -> binding.settingsUiDark
false -> binding.settingsUiLight
}
previous.alpha = 1f
fun uiTheme(mode: Boolean?, current: View) {
previous.alpha = 0.33f
previous = current
current.alpha = 1f
uiSettings.darkMode = mode
saveData("ui_settings", uiSettings)
Refresh.all()
finish()
startActivity(Intent(this, SettingsActivity::class.java))
initActivity(this)
}
binding.settingsUiAuto.setOnClickListener {
uiTheme(null, it)
}
binding.settingsUiLight.setOnClickListener {
uiTheme(false, it)
}
binding.settingsUiDark.setOnClickListener {
uiTheme(true, it)
}
var previousStart: View = when (uiSettings.defaultStartUpTab) {
0 -> binding.uiSettingsAnime
1 -> binding.uiSettingsHome
2 -> binding.uiSettingsManga
else -> binding.uiSettingsHome
}
previousStart.alpha = 1f
fun uiTheme(mode: Int, current: View) {
previousStart.alpha = 0.33f
previousStart = current
current.alpha = 1f
uiSettings.defaultStartUpTab = mode
saveData("ui_settings", uiSettings)
initActivity(this)
}
binding.uiSettingsAnime.setOnClickListener {
uiTheme(0, it)
}
binding.uiSettingsHome.setOnClickListener {
uiTheme(1, it)
}
binding.uiSettingsManga.setOnClickListener {
uiTheme(2, it)
}
binding.settingsShowYt.isChecked = uiSettings.showYtButton
binding.settingsShowYt.setOnCheckedChangeListener { _, isChecked ->
uiSettings.showYtButton = isChecked
saveData("ui_settings", uiSettings)
}
var previousEp: View = when (uiSettings.animeDefaultView) {
0 -> binding.settingsEpList
1 -> binding.settingsEpGrid
2 -> binding.settingsEpCompact
else -> binding.settingsEpList
}
previousEp.alpha = 1f
fun uiEp(mode: Int, current: View) {
previousEp.alpha = 0.33f
previousEp = current
current.alpha = 1f
uiSettings.animeDefaultView = mode
saveData("ui_settings", uiSettings)
}
binding.settingsEpList.setOnClickListener {
uiEp(0, it)
}
binding.settingsEpGrid.setOnClickListener {
uiEp(1, it)
}
binding.settingsEpCompact.setOnClickListener {
uiEp(2, it)
}
var previousChp: View = when (uiSettings.mangaDefaultView) {
0 -> binding.settingsChpList
1 -> binding.settingsChpCompact
else -> binding.settingsChpList
}
previousChp.alpha = 1f
fun uiChp(mode: Int, current: View) {
previousChp.alpha = 0.33f
previousChp = current
current.alpha = 1f
uiSettings.mangaDefaultView = mode
saveData("ui_settings", uiSettings)
}
binding.settingsChpList.setOnClickListener {
uiChp(0, it)
}
binding.settingsChpCompact.setOnClickListener {
uiChp(1, it)
}
binding.settingBuyMeCoffee.setOnClickListener {
lifecycleScope.launch {
it.pop()
}
openLinkInBrowser("https://www.buymeacoffee.com/rebelonion")
}
lifecycleScope.launch {
binding.settingBuyMeCoffee.pop()
}
binding.settingUPI.visibility = if (checkCountry(this)) View.VISIBLE else View.GONE
lifecycleScope.launch {
binding.settingUPI.pop()
}
binding.loginDiscord.setOnClickListener {
openLinkInBrowser(getString(R.string.discord))
}
binding.loginGithub.setOnClickListener {
openLinkInBrowser(getString(R.string.github))
}
binding.settingsUi.setOnClickListener {
startActivity(Intent(this, UserInterfaceSettingsActivity::class.java))
}
binding.settingsFAQ.setOnClickListener {
startActivity(Intent(this, FAQActivity::class.java))
}
(binding.settingsLogo.drawable as Animatable).start()
val array = resources.getStringArray(R.array.tips)
binding.settingsLogo.setSafeOnClickListener {
(binding.settingsLogo.drawable as Animatable).start()
snackString(array[(Math.random() * array.size).toInt()], this)
}
binding.settingsDev.setOnClickListener {
DevelopersDialogFragment().show(supportFragmentManager, "dialog")
}
binding.settingsForks.setOnClickListener {
ForksDialogFragment().show(supportFragmentManager, "dialog")
}
binding.settingsDisclaimer.setOnClickListener {
val title = getString(R.string.disclaimer)
val text = TextView(this)
text.setText(R.string.full_disclaimer)
CustomBottomDialog.newInstance().apply {
setTitleText(title)
addView(text)
setNegativeButton(currContext()!!.getString(R.string.close)) {
dismiss()
}
show(supportFragmentManager, "dialog")
}
}
var curTime = loadData<Int>("subscriptions_time") ?: defaultTime
val timeNames = timeMinutes.map {
val mins = it % 60
val hours = it / 60
if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}"
else getString(R.string.do_not_update)
}.toTypedArray()
binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[curTime])
val speedDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(R.string.subscriptions_checking_time)
binding.settingsSubscriptionsTime.setOnClickListener {
speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i ->
curTime = i
binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[i])
saveData("subscriptions_time", curTime)
dialog.dismiss()
startSubscription(true)
}.show()
}
binding.settingsSubscriptionsTime.setOnLongClickListener {
startSubscription(true)
true
}
binding.settingsNotificationsCheckingSubscriptions.isChecked = loadData("subscription_checking_notifications") ?: true
binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked ->
saveData("subscription_checking_notifications", isChecked)
if (isChecked)
Notifications.createChannel(
this,
null,
"subscription_checking",
getString(R.string.checking_subscriptions),
false
)
else
Notifications.deleteChannel(this, "subscription_checking")
}
binding.settingsNotificationsCheckingSubscriptions.setOnLongClickListener {
openSettings(this, null)
}
binding.settingsCheckUpdate.isChecked = loadData("check_update") ?: true
binding.settingsCheckUpdate.setOnCheckedChangeListener { _, isChecked ->
saveData("check_update", isChecked)
if (!isChecked) {
snackString(getString(R.string.long_click_to_check_update))
}
}
binding.settingsLogo.setOnLongClickListener {
lifecycleScope.launch(Dispatchers.IO) {
AppUpdater.check(this@SettingsActivity, true)
}
true
}
binding.settingsCheckUpdate.setOnLongClickListener {
lifecycleScope.launch(Dispatchers.IO) {
AppUpdater.check(this@SettingsActivity, true)
}
true
}
binding.settingsAccountHelp.setOnClickListener {
val title = getString(R.string.account_help)
val full = getString(R.string.full_account_help)
CustomBottomDialog.newInstance().apply {
setTitleText(title)
addView(
TextView(it.context).apply {
val markWon = Markwon.builder(it.context).usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
markWon.setMarkdown(this, full)
}
)
}.show(supportFragmentManager, "dialog")
}
fun reload() {
if (Anilist.token != null) {
binding.settingsAnilistLogin.setText(R.string.logout)
binding.settingsAnilistLogin.setOnClickListener {
Anilist.removeSavedToken(it.context)
restartMainActivity.isEnabled = true
reload()
}
binding.settingsAnilistUsername.visibility = View.VISIBLE
binding.settingsAnilistUsername.text = Anilist.username
binding.settingsAnilistAvatar.loadImage(Anilist.avatar)
binding.settingsMALLoginRequired.visibility = View.GONE
binding.settingsMALLogin.visibility = View.VISIBLE
binding.settingsMALUsername.visibility = View.VISIBLE
if (MAL.token != null) {
binding.settingsMALLogin.setText(R.string.logout)
binding.settingsMALLogin.setOnClickListener {
MAL.removeSavedToken(it.context)
restartMainActivity.isEnabled = true
reload()
}
binding.settingsMALUsername.visibility = View.VISIBLE
binding.settingsMALUsername.text = MAL.username
binding.settingsMALAvatar.loadImage(MAL.avatar)
} else {
binding.settingsMALAvatar.setImageResource(R.drawable.ic_round_person_24)
binding.settingsMALUsername.visibility = View.GONE
binding.settingsMALLogin.setText(R.string.login)
binding.settingsMALLogin.setOnClickListener {
MAL.loginIntent(this)
}
}
} else {
binding.settingsAnilistAvatar.setImageResource(R.drawable.ic_round_person_24)
binding.settingsAnilistUsername.visibility = View.GONE
binding.settingsAnilistLogin.setText(R.string.login)
binding.settingsAnilistLogin.setOnClickListener {
Anilist.loginIntent(this)
}
binding.settingsMALLoginRequired.visibility = View.VISIBLE
binding.settingsMALLogin.visibility = View.GONE
binding.settingsMALUsername.visibility = View.GONE
}
if (Discord.token != null) {
if (Discord.avatar != null) {
binding.settingsDiscordAvatar.loadImage(Discord.avatar)
}
binding.settingsDiscordUsername.visibility = View.VISIBLE
binding.settingsDiscordUsername.text = Discord.userid ?: Discord.token?.replace(Regex("."),"*")
binding.settingsDiscordLogin.setText(R.string.logout)
binding.settingsDiscordLogin.setOnClickListener {
Discord.removeSavedToken(this)
restartMainActivity.isEnabled = true
reload()
}
} else {
binding.settingsDiscordAvatar.setImageResource(R.drawable.ic_round_person_24)
binding.settingsDiscordUsername.visibility = View.GONE
binding.settingsDiscordLogin.setText(R.string.login)
binding.settingsDiscordLogin.setOnClickListener {
Discord.warning(this).show(supportFragmentManager, "dialog")
}
}
}
reload()
lifecycleScope.launch(Dispatchers.IO) {
delay(2000)
runOnUiThread {
if (Random.nextInt(0, 100) > 69) {
CustomBottomDialog.newInstance().apply {
title = "Enjoying the App?"
addView(TextView(this@SettingsActivity).apply {
text =
"Consider donating!\nOnce we reach the goal of $1000 (60%+ already reached!), Get ready to get an Offline Player & Manga Downloads!"
})
setNegativeButton("no moners :(") {
snackString("That's alright, you'll be a rich man soon :prayge:")
dismiss()
}
setPositiveButton("denote :)") {
if (binding.settingUPI.visibility == View.VISIBLE) binding.settingUPI.performClick()
else binding.settingBuyMeCoffee.performClick()
dismiss()
}
show(supportFragmentManager, "dialog")
}
}
}
}
}
}

View file

@ -0,0 +1,85 @@
package ani.dantotsu.settings
import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import ani.dantotsu.*
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.others.imagesearch.ImageSearchActivity
import ani.dantotsu.databinding.BottomSheetSettingsBinding
class SettingsDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetSettingsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = BottomSheetSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (Anilist.token != null) {
binding.settingsLogin.setText(R.string.logout)
binding.settingsLogin.setOnClickListener {
Anilist.removeSavedToken(it.context)
dismiss()
startMainActivity(requireActivity(),)
}
binding.settingsUsername.text = Anilist.username
binding.settingsUserAvatar.loadImage(Anilist.avatar)
} else {
binding.settingsUsername.visibility = View.GONE
binding.settingsLogin.setText(R.string.login)
binding.settingsLogin.setOnClickListener {
dismiss()
Anilist.loginIntent(requireActivity())
}
}
binding.settingsExtensionSettings.setSafeOnClickListener {
startActivity(Intent(activity, ExtensionsActivity::class.java))
dismiss()
}
binding.settingsSettings.setSafeOnClickListener {
startActivity(Intent(activity, SettingsActivity::class.java))
dismiss()
}
binding.settingsAnilistSettings.setOnClickListener {
openLinkInBrowser("https://anilist.co/settings/lists")
dismiss()
}
binding.imageSearch.setOnClickListener {
startActivity(Intent(activity, ImageSearchActivity::class.java))
dismiss()
}
binding.settingsDownloads.setSafeOnClickListener {
try {
val arrayOfFiles = ContextCompat.getExternalFilesDirs(requireContext(), null)
startActivity(
if (loadData<Boolean>("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
val parentDirectory = arrayOfFiles[1].toString()
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.parse(parentDirectory), "resource/folder")
} else Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
)
} catch (e: ActivityNotFoundException) {
toast(getString(R.string.file_manager_not_found))
}
dismiss()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View file

@ -0,0 +1,22 @@
package ani.dantotsu.settings
import java.io.Serializable
data class UserInterfaceSettings(
var darkMode: Boolean? = null,
var showYtButton: Boolean = true,
var animeDefaultView: Int = 0,
var mangaDefaultView: Int = 0,
//App
var immersiveMode: Boolean = false,
var smallView: Boolean = true,
var defaultStartUpTab: Int = 1,
var homeLayoutShow: MutableList<Boolean> = mutableListOf(true, false, false, true, false, false, true),
//Animations
var bannerAnimations: Boolean = true,
var layoutAnimations: Boolean = true,
var animationSpeed: Float = 1f
) : Serializable

View file

@ -0,0 +1,107 @@
package ani.dantotsu.settings
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import ani.dantotsu.*
import ani.dantotsu.databinding.ActivityUserInterfaceSettingsBinding
import com.google.android.material.snackbar.Snackbar
class UserInterfaceSettingsActivity : AppCompatActivity() {
lateinit var binding: ActivityUserInterfaceSettingsBinding
private val ui = "ui_settings"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
initActivity(this)
binding.uiSettingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
val settings = loadData<UserInterfaceSettings>(ui, toast = false) ?: UserInterfaceSettings().apply { saveData(ui, this) }
binding.uiSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
val views = resources.getStringArray(R.array.home_layouts)
binding.uiSettingsHomeLayout.setOnClickListener {
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.home_layout_show)).apply {
setMultiChoiceItems(views, settings.homeLayoutShow.toBooleanArray()) { _, i, value ->
settings.homeLayoutShow[i] = value
saveData(ui, settings)
}
}.show()
}
binding.uiSettingsSmallView.isChecked = settings.smallView
binding.uiSettingsSmallView.setOnCheckedChangeListener { _, isChecked ->
settings.smallView = isChecked
saveData(ui, settings)
restartApp()
}
binding.uiSettingsImmersive.isChecked = settings.immersiveMode
binding.uiSettingsImmersive.setOnCheckedChangeListener { _, isChecked ->
settings.immersiveMode = isChecked
saveData(ui, settings)
restartApp()
}
binding.uiSettingsBannerAnimation.isChecked = settings.bannerAnimations
binding.uiSettingsBannerAnimation.setOnCheckedChangeListener { _, isChecked ->
settings.bannerAnimations = isChecked
saveData(ui, settings)
restartApp()
}
binding.uiSettingsLayoutAnimation.isChecked = settings.layoutAnimations
binding.uiSettingsLayoutAnimation.setOnCheckedChangeListener { _, isChecked ->
settings.layoutAnimations = isChecked
saveData(ui, settings)
restartApp()
}
val map = mapOf(
2f to 0.5f,
1.75f to 0.625f,
1.5f to 0.75f,
1.25f to 0.875f,
1f to 1f,
0.75f to 1.25f,
0.5f to 1.5f,
0.25f to 1.75f,
0f to 0f
)
val mapReverse = map.map { it.value to it.key }.toMap()
binding.uiSettingsAnimationSpeed.value = mapReverse[settings.animationSpeed] ?: 1f
binding.uiSettingsAnimationSpeed.addOnChangeListener { _, value, _ ->
settings.animationSpeed = map[value] ?: 1f
saveData(ui, settings)
restartApp()
}
}
private fun restartApp() {
Snackbar.make(
binding.root,
R.string.restart_app, Snackbar.LENGTH_SHORT
).apply {
val mainIntent =
Intent.makeRestartActivityTask(context.packageManager.getLaunchIntentForPackage(context.packageName)!!.component)
setAction("Do it!") {
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
show()
}
}
}