Source settings

This commit is contained in:
tom5079
2021-05-18 10:39:35 +09:00
parent 5a19fb8336
commit dd60a1fdfb
27 changed files with 251 additions and 122 deletions

View File

@@ -237,7 +237,6 @@
android:pathPattern="/..*" /> android:pathPattern="/..*" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
</application> </application>
</manifest> </manifest>

View File

@@ -65,9 +65,9 @@ class Pupil : Application(), DIAware {
import(androidXModule(this@Pupil)) import(androidXModule(this@Pupil))
import(sourceModule) import(sourceModule)
bind<OkHttpClient>() with provider { client } bind { provider { client } }
bind<ImageCache>() with singleton { ImageCache(this@Pupil) } bind { singleton { ImageCache(this@Pupil) } }
bind<DownloadManager>() with singleton { DownloadManager(this@Pupil) } bind { singleton { DownloadManager(this@Pupil) } }
bind<SavedSourceSet>(tag = "histories") with singleton { SavedSourceSet(File(ContextCompat.getDataDir(this@Pupil), "histories.json")) } bind<SavedSourceSet>(tag = "histories") with singleton { SavedSourceSet(File(ContextCompat.getDataDir(this@Pupil), "histories.json")) }
bind<SavedSourceSet>(tag = "favorites") with singleton { SavedSourceSet(File(ContextCompat.getDataDir(this@Pupil), "favorites.json")) } bind<SavedSourceSet>(tag = "favorites") with singleton { SavedSourceSet(File(ContextCompat.getDataDir(this@Pupil), "favorites.json")) }
@@ -99,7 +99,7 @@ class Pupil : Application(), DIAware {
try { try {
Preferences.get<String>("download_folder").also { Preferences.get<String>("download_folder").also {
if (it.startsWith("content") && Build.VERSION.SDK_INT > 19) if (it.startsWith("content"))
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
Uri.parse(it), Uri.parse(it),
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

View File

@@ -93,16 +93,6 @@ class SearchResultsAdapter(var results: LiveData<List<ItemInfo>>) : RecyclerSwip
}) })
binding.tagGroup.onClickListener = onChipClickedHandler binding.tagGroup.onClickListener = onChipClickedHandler
/*
CoroutineScope(Dispatchers.Main).launch {
while (true) {
updateProgress()
delay(1000)
}
}
*/
} }
private val controllerListener = object: BaseControllerListener<ImageInfo>() { private val controllerListener = object: BaseControllerListener<ImageInfo>() {
@@ -119,21 +109,6 @@ class SearchResultsAdapter(var results: LiveData<List<ItemInfo>>) : RecyclerSwip
} }
} }
private fun updateProgress() {
/* TODO
binding.root.max = cache.metadata.imageList?.size ?: 0
binding.root.progress = cache.metadata.imageList?.count { it != null } ?: 0
binding.root.type = if (cache.metadata.imageList?.all { it != null } == true) { // Download completed
if (DownloadManager.getInstance(itemView.context).getDownloadFolder(source, itemID) != null)
ProgressCardView.Type.DOWNLOAD
else
ProgressCardView.Type.CACHE
} else
ProgressCardView.Type.LOADING
*/
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun bind(result: ItemInfo) { fun bind(result: ItemInfo) {
source = result.source source = result.source
@@ -210,7 +185,6 @@ class SearchResultsAdapter(var results: LiveData<List<ItemInfo>>) : RecyclerSwip
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) ViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
@ExperimentalTime
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
mItemManger.bindView(holder.itemView, position) mItemManger.bindView(holder.itemView, position)
holder.bind(results.value!![position]) holder.bind(results.value!![position])

View File

@@ -30,6 +30,7 @@ import xyz.quaver.pupil.sources.SourceEntries
class SourceAdapter(sources: SourceEntries) : RecyclerView.Adapter<SourceAdapter.ViewHolder>() { class SourceAdapter(sources: SourceEntries) : RecyclerView.Adapter<SourceAdapter.ViewHolder>() {
var onSourceSelectedListener: ((String) -> Unit)? = null var onSourceSelectedListener: ((String) -> Unit)? = null
var onSourceSettingsSelectedListener: ((String) -> Unit)? = null
private val sources = sources.toList() private val sources = sources.toList()
@@ -40,6 +41,9 @@ class SourceAdapter(sources: SourceEntries) : RecyclerView.Adapter<SourceAdapter
binding.go.setOnClickListener { binding.go.setOnClickListener {
onSourceSelectedListener?.invoke(source.name) onSourceSelectedListener?.invoke(source.name)
} }
binding.settings.setOnClickListener {
onSourceSettingsSelectedListener?.invoke(source.name)
}
} }
fun bind(source: AnySource) { fun bind(source: AnySource) {

View File

@@ -18,9 +18,6 @@
package xyz.quaver.pupil.sources package xyz.quaver.pupil.sources
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -120,6 +117,7 @@ typealias AnySource = Source<*, SearchSuggestion>
abstract class Source<Query_SortMode: Enum<Query_SortMode>, Suggestion: SearchSuggestion> { abstract class Source<Query_SortMode: Enum<Query_SortMode>, Suggestion: SearchSuggestion> {
abstract val name: String abstract val name: String
abstract val iconResID: Int abstract val iconResID: Int
abstract val preferenceID: Int
abstract val availableSortMode: Array<Query_SortMode> abstract val availableSortMode: Array<Query_SortMode>
abstract suspend fun search(query: String, range: IntRange, sortMode: Enum<*>) : Pair<Channel<ItemInfo>, Int> abstract suspend fun search(query: String, range: IntRange, sortMode: Enum<*>) : Pair<Channel<ItemInfo>, Int>
@@ -138,15 +136,19 @@ abstract class Source<Query_SortMode: Enum<Query_SortMode>, Suggestion: SearchSu
typealias SourceEntry = Pair<String, AnySource> typealias SourceEntry = Pair<String, AnySource>
typealias SourceEntries = Set<SourceEntry> typealias SourceEntries = Set<SourceEntry>
typealias PreferenceID = Pair<String, Int>
typealias PreferenceIDs = Set<PreferenceID>
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val sourceModule = DI.Module(name = "source") { val sourceModule = DI.Module(name = "source") {
bind() from setBinding<SourceEntry>() bindSet<SourceEntry>()
bindSet<PreferenceID>()
listOf( listOf(
Hitomi() Hitomi()
).forEach { source -> ).forEach { source ->
bind<SourceEntry>().inSet() with multiton { _: Unit -> source.name to (source as AnySource) } inSet { multiton { _: Unit -> source.name to (source as AnySource) } }
inSet { singleton { source.name to source.preferenceID } }
} }
bind<History>() with factory { source: String -> History(di, source) } bind { factory { source: String -> History(di, source) } }
} }

View File

@@ -26,6 +26,7 @@ import org.kodein.di.DI
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.instance import org.kodein.di.instance
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.SavedSourceSet import xyz.quaver.pupil.util.SavedSourceSet
import xyz.quaver.pupil.util.source import xyz.quaver.pupil.util.source
@@ -38,6 +39,8 @@ class History(override val di: DI, source: String) : Source<DefaultSortMode, Sea
get() = source.name get() = source.name
override val iconResID: Int override val iconResID: Int
get() = source.iconResID get() = source.iconResID
override val preferenceID: Int
get() = source.preferenceID
override val availableSortMode: Array<DefaultSortMode> = DefaultSortMode.values() override val availableSortMode: Array<DefaultSortMode> = DefaultSortMode.values()
override suspend fun search(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<ItemInfo>, Int> { override suspend fun search(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<ItemInfo>, Int> {

View File

@@ -29,6 +29,7 @@ import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.ItemInfo.ExtraType import xyz.quaver.pupil.sources.ItemInfo.ExtraType
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.translations import xyz.quaver.pupil.util.translations
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
import kotlin.math.max import kotlin.math.max
@@ -56,6 +57,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
override val name: String = "hitomi.la" override val name: String = "hitomi.la"
override val iconResID: Int = R.drawable.hitomi override val iconResID: Int = R.drawable.hitomi
override val preferenceID: Int = R.xml.hitomi_preferences
override val availableSortMode: Array<SortMode> = SortMode.values() override val availableSortMode: Array<SortMode> = SortMode.values()
var cachedQuery: String? = null var cachedQuery: String? = null
@@ -67,7 +69,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
cachedQuery = null cachedQuery = null
cache.clear() cache.clear()
yield() yield()
doSearch(query, sortMode == SortMode.POPULAR).let { doSearch("$query ${Preferences["hitomi.default_query", ""]}", sortMode == SortMode.POPULAR).let {
yield() yield()
cache.addAll(it) cache.addAll(it)
} }

View File

@@ -45,7 +45,7 @@ import com.google.android.material.snackbar.Snackbar
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.di import org.kodein.di.android.closestDI
import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.FloatingSearchView
import xyz.quaver.pupil.* import xyz.quaver.pupil.*
import xyz.quaver.pupil.adapters.SearchResultsAdapter import xyz.quaver.pupil.adapters.SearchResultsAdapter
@@ -66,11 +66,13 @@ class MainActivity :
NavigationView.OnNavigationItemSelectedListener, NavigationView.OnNavigationItemSelectedListener,
DIAware DIAware
{ {
override val di by di() override val di by closestDI()
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
private val model: MainViewModel by viewModels() private val model: MainViewModel by viewModels()
private var refreshOnResume = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = MainActivityBinding.inflate(layoutInflater) binding = MainActivityBinding.inflate(layoutInflater)
@@ -165,6 +167,15 @@ class MainActivity :
} } } }
} }
override fun onResume() {
super.onResume()
if (refreshOnResume) {
model.query()
refreshOnResume = false
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding.contents.recyclerview.adapter = null binding.contents.recyclerview.adapter = null
@@ -440,6 +451,13 @@ class MainActivity :
dismiss() dismiss()
} }
onSourceSettingsSelectedListener = {
startActivity(Intent(this@MainActivity, SettingsActivity::class.java).putExtra(SettingsActivity.SETTINGS_EXTRA, it))
refreshOnResume = true
dismiss()
}
}.show(supportFragmentManager, null) }.show(supportFragmentManager, null)
} }
} }

View File

@@ -22,16 +22,27 @@ import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.fragment.SettingsFragment import xyz.quaver.pupil.ui.fragment.SettingsFragment
import xyz.quaver.pupil.ui.fragment.SourceSettingsFragment
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
companion object {
const val SETTINGS_EXTRA = "xyz.quaver.pupil.ui.SettingsActivity.SETTINGS_EXTRA"
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity) setContentView(R.layout.settings_activity)
val fragment = intent.getStringExtra(SETTINGS_EXTRA)?.run {
SourceSettingsFragment(this)
} ?: SettingsFragment()
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.settings, SettingsFragment()) .replace(R.id.settings, fragment)
.commit() .commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }

View File

@@ -31,7 +31,7 @@ import xyz.quaver.pupil.sources.Hitomi
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
class DefaultQueryDialogFragment() : DialogFragment() { class DefaultQueryDialogFragment : DialogFragment() {
// TODO languageMap // TODO languageMap
private val languages = Hitomi.languageMap private val languages = Hitomi.languageMap
private val reverseLanguages = languages.entries.associate { (k, v) -> v to k } private val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
@@ -85,7 +85,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
private fun initView() { private fun initView() {
val tags = Tags.parse( val tags = Tags.parse(
Preferences["default_query"] Preferences["hitomi.default_query"]
) )
with (binding.languageSelector) { with (binding.languageSelector) {
@@ -153,7 +153,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
s.replace( s.replace(
0, 0,
s.length, s.length,
s.toString().toLowerCase(java.util.Locale.getDefault()) s.toString().lowercase()
) )
} }
}) })

View File

@@ -21,7 +21,6 @@ package xyz.quaver.pupil.ui.dialog
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@@ -30,21 +29,21 @@ import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.di import org.kodein.di.android.x.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.toFile import xyz.quaver.io.util.toFile
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.DownloadLocationDialogBinding import xyz.quaver.pupil.databinding.DownloadLocationDialogBinding
import xyz.quaver.pupil.databinding.DownloadLocationItemBinding import xyz.quaver.pupil.databinding.DownloadLocationItemBinding
import xyz.quaver.pupil.util.DownloadManager
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.byteToString import xyz.quaver.pupil.util.byteToString
import xyz.quaver.pupil.util.DownloadManager
import java.io.File import java.io.File
class DownloadLocationDialogFragment : DialogFragment(), DIAware { class DownloadLocationDialogFragment : DialogFragment(), DIAware {
override val di by di() override val di by closestDI()
private val downloadManager: DownloadManager by instance() private val downloadManager: DownloadManager by instance()

View File

@@ -41,7 +41,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.di import org.kodein.di.android.x.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.SearchResultsAdapter import xyz.quaver.pupil.adapters.SearchResultsAdapter
@@ -60,7 +60,7 @@ import kotlin.collections.ArrayList
class GalleryDialogFragment(private val source: String, private val itemID: String) : DialogFragment(), DIAware { class GalleryDialogFragment(private val source: String, private val itemID: String) : DialogFragment(), DIAware {
override val di by di() override val di by closestDI()
private val favoriteTags: SavedSourceSet by instance(tag = "favoriteTags") private val favoriteTags: SavedSourceSet by instance(tag = "favoriteTags")

View File

@@ -34,12 +34,14 @@ import xyz.quaver.pupil.adapters.SourceAdapter
import xyz.quaver.pupil.sources.AnySource import xyz.quaver.pupil.sources.AnySource
import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.SourceEntries import xyz.quaver.pupil.sources.SourceEntries
import xyz.quaver.pupil.util.ItemClickSupport
class SourceSelectDialog : DialogFragment(), DIAware { class SourceSelectDialog : DialogFragment(), DIAware {
override val di by closestDI() override val di by closestDI()
var onSourceSelectedListener: ((String) -> Unit)? = null var onSourceSelectedListener: ((String) -> Unit)? = null
var onSourceSettingsSelectedListener: ((String) -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return Dialog(requireContext()).apply { return Dialog(requireContext()).apply {
@@ -50,6 +52,7 @@ class SourceSelectDialog : DialogFragment(), DIAware {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = SourceAdapter(direct.instance()).apply { adapter = SourceAdapter(direct.instance()).apply {
onSourceSelectedListener = this@SourceSelectDialog.onSourceSelectedListener onSourceSelectedListener = this@SourceSelectDialog.onSourceSelectedListener
onSourceSettingsSelectedListener = this@SourceSelectDialog.onSourceSettingsSelectedListener
} }
}) })
} }

View File

@@ -26,11 +26,8 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.* import androidx.preference.*
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.di import org.kodein.di.android.x.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.getChild
@@ -39,7 +36,6 @@ import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.ui.SettingsActivity import xyz.quaver.pupil.ui.SettingsActivity
import xyz.quaver.pupil.ui.dialog.* import xyz.quaver.pupil.ui.dialog.*
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.DownloadManager
import java.util.* import java.util.*
class SettingsFragment : class SettingsFragment :
@@ -49,7 +45,7 @@ class SettingsFragment :
SharedPreferences.OnSharedPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener,
DIAware { DIAware {
override val di by di() override val di by closestDI()
private val downloadManager: DownloadManager by instance() private val downloadManager: DownloadManager by instance()
@@ -92,14 +88,6 @@ class SettingsFragment :
"download_folder" -> { "download_folder" -> {
DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog") DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog")
} }
"default_query" -> {
DefaultQueryDialogFragment().apply {
onPositiveButtonClickListener = { newTags ->
Preferences["default_query"] = newTags.toString()
summary = newTags.toString()
}
}.show(parentFragmentManager, "Default Query Dialog")
}
"app_lock" -> { "app_lock" -> {
val intent = Intent(requireContext(), LockActivity::class.java).apply { val intent = Intent(requireContext(), LockActivity::class.java).apply {
putExtra("force", true) putExtra("force", true)
@@ -127,9 +115,6 @@ class SettingsFragment :
this ?: return false this ?: return false
when (key) { when (key) {
"tag_translation" -> {
updateTranslations()
}
"nomedia" -> { "nomedia" -> {
val create = (newValue as? Boolean) ?: return false val create = (newValue as? Boolean) ?: return false
@@ -228,11 +213,6 @@ class SettingsFragment :
onPreferenceChangeListener = this@SettingsFragment onPreferenceChangeListener = this@SettingsFragment
} }
"default_query" -> {
summary = Preferences.get<String>("default_query")
onPreferenceClickListener = this@SettingsFragment
}
"app_lock" -> { "app_lock" -> {
val lockManager = LockManager(requireContext()) val lockManager = LockManager(requireContext())
summary = summary =
@@ -250,27 +230,6 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"tag_translation" -> {
this as ListPreference
isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val languages = getAvailableLanguages().distinct().toTypedArray()
entries = languages.map { Locale(it).let { loc -> loc.getDisplayLanguage(loc) } }.toTypedArray()
entryValues = languages
launch(Dispatchers.Main) {
isEnabled = true
}
}
}
onPreferenceChangeListener = this@SettingsFragment
}
"proxy" -> { "proxy" -> {
summary = getProxyInfo().type.name summary = getProxyInfo().type.name

View File

@@ -0,0 +1,130 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.fragment
import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.pupil.sources.PreferenceIDs
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialogFragment
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.getAvailableLanguages
import xyz.quaver.pupil.util.updateTranslations
import java.util.*
class SourceSettingsFragment(private val source: String) :
PreferenceFragmentCompat(),
Preference.OnPreferenceClickListener,
Preference.OnPreferenceChangeListener,
DIAware {
override val di by closestDI()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(direct.instance<PreferenceIDs>().toMap()[source]!!, rootKey)
initPreferences()
}
override fun onPreferenceClick(preference: Preference?): Boolean {
with (preference) {
this ?: return false
when (key) {
"hitomi.default_query" -> {
DefaultQueryDialogFragment().apply {
onPositiveButtonClickListener = { newTags ->
Preferences["hitomi.default_query"] = newTags.toString()
summary = newTags.toString()
}
}.show(parentFragmentManager, "Default Query Dialog")
}
}
}
return true
}
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
with (preference) {
this ?: return false
when (key) {
"hitomi.tag_translation" -> {
updateTranslations()
}
else -> return false
}
}
return true
}
private fun initPreferences() {
for (i in 0 until preferenceScreen.preferenceCount) {
preferenceScreen.getPreference(i).run {
if (this is PreferenceCategory)
(0 until preferenceCount).map { getPreference(it) }
else
listOf(this)
}.forEach { preference ->
with (preference) {
when (key) {
"hitomi.default_query" -> {
summary = Preferences.get<String>(key)
onPreferenceClickListener = this@SourceSettingsFragment
}
"hitomi.tag_translation" -> {
this as ListPreference
isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val languages = getAvailableLanguages().distinct().toTypedArray()
entries = languages.map { Locale(it).let { loc -> loc.getDisplayLanguage(loc) } }.toTypedArray()
entryValues = languages
launch(Dispatchers.Main) {
isEnabled = true
}
}
}
onPreferenceChangeListener = this@SourceSettingsFragment
}
}
}
}
}
}
}

View File

@@ -74,7 +74,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
s ?: return s ?: return
if (s.any { it.isUpperCase() }) if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault())) s.replace(0, s.length, s.toString().lowercase())
} }
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) { override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {

View File

@@ -25,15 +25,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.di import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import xyz.quaver.pupil.sources.AnySource import xyz.quaver.pupil.sources.AnySource
import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.ItemInfo
import xyz.quaver.pupil.util.source import xyz.quaver.pupil.util.source
class GalleryDialogViewModel(app: Application) : AndroidViewModel(app), DIAware { class GalleryDialogViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by di() override val di by closestDI()
private val _info = MutableLiveData<ItemInfo>() private val _info = MutableLiveData<ItemInfo>()
val info: LiveData<ItemInfo> = _info val info: LiveData<ItemInfo> = _info

View File

@@ -128,7 +128,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
queryJob = viewModelScope.launch { queryJob = viewModelScope.launch {
val channel = withContext(Dispatchers.IO) { val channel = withContext(Dispatchers.IO) {
val (channel, count) = source.search( val (channel, count) = source.search(
(query.value ?: "") + Preferences["default_query", ""], query.value ?: "",
(currentPage - 1) * perPage until currentPage * perPage, (currentPage - 1) * perPage until currentPage * perPage,
sortMode sortMode
) )

View File

@@ -28,27 +28,24 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.di import org.kodein.di.android.closestDI
import org.kodein.di.instance
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.* import xyz.quaver.io.util.*
import xyz.quaver.pupil.sources.AnySource import xyz.quaver.pupil.sources.AnySource
class DownloadManager constructor(context: Context) : ContextWrapper(context), DIAware { class DownloadManager constructor(context: Context) : ContextWrapper(context), DIAware {
override val di by di(context) override val di by closestDI(context)
private val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!) private val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!)
val downloadFolder: FileX val downloadFolder: FileX
get() = { get() = kotlin.runCatching {
kotlin.runCatching { FileX(this, Preferences.get<String>("download_folder"))
FileX(this, Preferences.get<String>("download_folder")) }.getOrElse {
}.getOrElse { Preferences["download_folder"] = defaultDownloadFolder.uri.toString()
Preferences["download_folder"] = defaultDownloadFolder.uri.toString() defaultDownloadFolder
defaultDownloadFolder }
}
}.invoke()
private var prevDownloadFolder: FileX? = null private var prevDownloadFolder: FileX? = null
private var downloadFolderMapInstance: MutableMap<String, String>? = null private var downloadFolderMapInstance: MutableMap<String, String>? = null
@@ -57,21 +54,19 @@ class DownloadManager constructor(context: Context) : ContextWrapper(context), D
get() { get() {
if (prevDownloadFolder != downloadFolder) { if (prevDownloadFolder != downloadFolder) {
prevDownloadFolder = downloadFolder prevDownloadFolder = downloadFolder
downloadFolderMapInstance = { downloadFolderMapInstance = run {
val file = downloadFolder.getChild(".download") val file = downloadFolder.getChild(".download")
val data = if (file.exists()) val data = if (file.exists())
kotlin.runCatching { kotlin.runCatching {
file.readText()?.let { Json.decodeFromString<MutableMap<String, String>>(it) } file.readText()?.let<String, MutableMap<String, String>> { Json.decodeFromString(it) }
}.onFailure { file.delete() }.getOrNull() }.onFailure { file.delete() }.getOrNull()
else else
null null
data ?: run {
data ?: {
file.createNewFile() file.createNewFile()
mutableMapOf<String, String>() mutableMapOf()
}.invoke() }
}.invoke() }
} }
return downloadFolderMapInstance ?: mutableMapOf() return downloadFolderMapInstance ?: mutableMapOf()

View File

@@ -23,11 +23,11 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.sendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import okhttp3.* import okhttp3.*
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.di import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -35,7 +35,7 @@ import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class ImageCache(context: Context) : DIAware { class ImageCache(context: Context) : DIAware {
override val di by di(context) override val di by closestDI(context)
private val client: OkHttpClient by instance() private val client: OkHttpClient by instance()
@@ -115,7 +115,7 @@ class ImageCache(context: Context) : DIAware {
file.createNewFile() file.createNewFile()
body.byteStream().copyTo(file.outputStream()) { bytes, _ -> body.byteStream().copyTo(file.outputStream()) { bytes, _ ->
channel.sendBlocking(bytes / body.contentLength().toFloat() * 100) channel.trySendBlocking(bytes / body.contentLength().toFloat() * 100)
} }
} }

View File

@@ -45,7 +45,7 @@ fun updateTranslations() = CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching { kotlin.runCatching {
translations = Json.decodeFromString<Map<String, String>>(client.newCall( translations = Json.decodeFromString<Map<String, String>>(client.newCall(
Request.Builder() Request.Builder()
.url(contentURL + "${Preferences["tag_translation", ""].let { if (it.isEmpty()) Locale.getDefault().language else it }}.json") .url(contentURL + "${Preferences["hitomi.tag_translation", ""].let { if (it.isEmpty()) Locale.getDefault().language else it }}.json")
.build() .build()
).execute().also { if (it.code != 200) return@launch }.body?.use { it.string() } ?: return@launch).filterValues { it.isNotEmpty() } ).execute().also { if (it.code != 200) return@launch }.body?.use { it.string() } ?: return@launch).filterValues { it.isNotEmpty() }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 B

After

Width:  |  Height:  |  Size: 0 B

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2021 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:key="hitomi.default_query"
app:title="@string/settings_default_query"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:key="hitomi.tag_translation"
app:title="@string/settings_tag_translation"
app:useSimpleSummaryProvider="true"/>
</PreferenceScreen>