Migrate to Tachiyomi 6.1

Rewrite batch add UI
Rewrite lock UI
This commit is contained in:
NerdNumber9 2017-08-24 12:28:54 -04:00
parent 3da7c47bf5
commit 32d02f9329
28 changed files with 640 additions and 532 deletions

View File

@ -42,8 +42,8 @@ android {
minSdkVersion 16
targetSdkVersion 25
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 5003
versionName "v5.0.3-EH"
versionCode 6101
versionName "v6.1.1-EH"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@ -214,7 +214,7 @@ dependencies {
releaseCompile "com.google.firebase:firebase-crash:$firebase_version"
//SnappyDB (EH)
compile 'io.paperdb:paperdb:2.0'
compile 'io.paperdb:paperdb:2.1'
//JVE (Regex) (EH)
compile 'ru.lanwen.verbalregex:java-verbal-expressions:1.4'

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="eu.kanade.tachiyomi">
<uses-permission android:name="android.permission.INTERNET" />
@ -106,8 +107,8 @@
<!-- EH -->
<activity
android:name="exh.ui.login.LoginActivity"
android:label="@string/label_login"
android:parentActivityName=".ui.setting.SettingsActivity" >
android:label="@string/label_login">
<!-- TODO parent activity -->
</activity>
<activity
@ -145,17 +146,8 @@
android:scheme="https"/>
</intent-filter>
</activity>
<activity
android:name="exh.ui.migration.MigrationCompletionActivity"
android:label="Complete migration">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json"/>
</intent-filter>
</activity>
<activity android:name="exh.ui.lock.LockActivity"
<activity android:name="exh.ui.lock.LockController"
android:label="Application locked"/>
</application>

View File

@ -67,7 +67,7 @@ class NHentai(context: Context) : HttpSource() {
= parseResultPage(response)
override fun mangaDetailsParse(response: Response)
= parseGallery(jsonParser.parse(response.body().string()).asJsonObject)
= parseGallery(jsonParser.parse(response.body()!!.string()).asJsonObject)
//Used so we can use a different URL for fetching manga details and opening the details in the browser
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
@ -85,7 +85,7 @@ class NHentai(context: Context) : HttpSource() {
= nhGet(baseUrl + "/api/gallery/" + url.split("/").last())
fun parseResultPage(response: Response): MangasPage {
val res = jsonParser.parse(response.body().string()).asJsonObject
val res = jsonParser.parse(response.body()!!.string()).asJsonObject
val error = res.get("error")
if(error == null) {
@ -154,7 +154,7 @@ class NHentai(context: Context) : HttpSource() {
?: client.newCall(urlToDetailsRequest(url))
.asObservableSuccess()
.map {
rawParseGallery(jsonParser.parse(it.body().string()).asJsonObject)
rawParseGallery(jsonParser.parse(it.body()!!.string()).asJsonObject)
}.toBlocking().first()
}!!

View File

@ -4,54 +4,8 @@ import android.support.v7.app.AppCompatActivity
import eu.kanade.tachiyomi.util.LocaleHelper
abstract class BaseActivity : AppCompatActivity() {
init {
@Suppress("LeakingThis")
LocaleHelper.updateConfiguration(this)
}
var willLock = false
var disableLock = false
override fun onRestart() {
super.onRestart()
if(willLock && lockEnabled() && !disableLock) {
showLockActivity(this)
}
willLock = false
}
override fun onStop() {
super.onStop()
tryLock()
}
fun tryLock() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val mUsageStatsManager = getSystemService("usagestats") as UsageStatsManager
val time = System.currentTimeMillis()
// We get usage stats for the last 20 seconds
val stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 20, time)
// Sort the stats by the last time used
if (stats != null) {
val mySortedMap = TreeMap<Long, UsageStats>()
for (usageStats in stats) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (!mySortedMap.isEmpty()) {
if(mySortedMap[mySortedMap.lastKey()]?.packageName != packageName) {
willLock = true
}
}
}
} else {
val am = getSystemService(Service.ACTIVITY_SERVICE) as ActivityManager
val tasks: List<ActivityManager.RunningTaskInfo>
tasks = am.getRunningTasks(1)
val running = tasks[0]
if (running.topActivity.packageName != packageName) {
willLock = true
}
}
}
}

View File

@ -16,12 +16,6 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
*/
private var mangas: List<LibraryItem> = emptyList()
//EH
private val sourceManager: SourceManager by injectLazy()
private val searchEngine = SearchEngine()
private val metadataHelper = MetadataHelper()
var asyncSearchText: String? = null
/**
@ -36,49 +30,6 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
performFilter()
}
// --> EH
/**
* Filters the list of manga applying [filterObject] for each element.
*
* @param param the filter. Not used.
*/
override fun updateDataSet(param: String?) {
//Async search filter (EH)
val filtered = asyncSearchText?.let { search ->
mangas.filter {
filterObject(it, search)
}
} ?: mangas
//The rest of the filters run on the main loop
Handler(Looper.getMainLooper()).post {
filterItems(filtered)
notifyDataSetChanged()
}
}
/**
* Filters a manga depending on a query.
*
* @param manga the manga to filter.
* @param query the query to apply.
* @return true if the manga should be included, false otherwise.
*/
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
if(!isLewdSource(manga.source)) {
//Regular searching for normal manga
title.toLowerCase().contains(query) ||
author != null && author!!.toLowerCase().contains(query)
} else {
//Use gallery search engine for EH manga
val metadata = metadataHelper.fetchMetadata(manga.url, manga.source)
metadata?.let {
searchEngine.matches(it, searchEngine.parseQuery(query))
} ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
}
}
// <-- EH
/**
* Returns the position in the adapter for the given manga.
*
@ -89,7 +40,9 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
}
fun performFilter() {
updateDataSet(mangas.filter { it.filter(searchText) })
updateDataSet(mangas.filter {
it.filter(searchText)
})
}
}

View File

@ -102,19 +102,6 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onBind(category: Category) {
this.category = category
//TODO Fix
// --> EH
val presenter = fragment.presenter
searchSubscription = presenter
.searchSubject
.debounce(10L, TimeUnit.MILLISECONDS)
.subscribe { text -> //Debounce search (EH)
adapter.asyncSearchText = text?.trim()?.toLowerCase()
adapter.updateDataSet()
}
// <-- EH
adapter.mode = if (controller.selectedMangas.isNotEmpty()) {
FlexibleAdapter.MODE_MULTI
} else {

View File

@ -12,10 +12,18 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.isLewdSource
import exh.metadata.MetadataHelper
import exh.search.SearchEngine
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
class LibraryItem(val manga: Manga) : AbstractFlexibleItem<LibraryHolder>(), IFilterable {
// --> EH
private val searchEngine = SearchEngine()
private val metadataHelper = MetadataHelper()
// <-- EH
override fun getLayoutRes(): Int {
return R.layout.catalogue_grid_item
}
@ -53,6 +61,15 @@ class LibraryItem(val manga: Manga) : AbstractFlexibleItem<LibraryHolder>(), IFi
* @return true if the manga should be included, false otherwise.
*/
override fun filter(constraint: String): Boolean {
// --> EH
if(!isLewdSource(manga.source)) {
//Use gallery search engine for EH manga
metadataHelper.fetchMetadata(manga.url, manga.source)?.let {
return searchEngine.matches(it, searchEngine.parseQuery(constraint))
}
}
// <-- EH
return manga.title.contains(constraint, true) ||
(manga.author?.contains(constraint, true) ?: false)
}

View File

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.ui.main
import android.animation.ObjectAnimator
import android.app.ActivityManager
import android.app.Service
import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
@ -10,6 +15,8 @@ import android.support.v7.graphics.drawable.DrawerArrowDrawable
import android.view.ViewGroup
import com.bluelinelabs.conductor.*
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -26,8 +33,14 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import exh.ui.batchadd.BatchAddController
import exh.ui.lock.LockChangeHandler
import exh.ui.lock.LockController
import exh.ui.lock.lockEnabled
import exh.ui.lock.notifyLockSecurity
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.injectLazy
import java.util.*
class MainActivity : BaseActivity() {
@ -86,9 +99,8 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_recently_read -> setRoot(RecentlyReadController(), id)
R.id.nav_drawer_catalogues -> setRoot(CatalogueController(), id)
R.id.nav_drawer_latest_updates -> setRoot(LatestUpdatesController(), id)
//TODO
// --> EH
R.id.nav_drawer_batch_add -> setFragment(BatchAddFragment.newInstance(), id)
R.id.nav_drawer_batch_add -> setRoot(BatchAddController(), id)
// <-- EH
R.id.nav_drawer_downloads -> {
router.pushController(RouterTransaction.with(DownloadController())
@ -137,6 +149,17 @@ class MainActivity : BaseActivity() {
})
//Show lock
if (savedInstanceState == null) {
val lockEnabled = lockEnabled(preferences)
if (lockEnabled) {
doLock()
//Check lock security
notifyLockSecurity(this)
}
}
syncActivityViewWithController(router.backstack.lastOrNull()?.controller())
if (savedInstanceState == null) {
@ -144,15 +167,6 @@ class MainActivity : BaseActivity() {
if (Migrations.upgrade(preferences)) {
ChangelogDialogController().showDialog(router)
}
//Show lock
val lockEnabled = lockEnabled(preferences)
if(lockEnabled) {
showLockActivity(this)
//Check lock security
notifyLockSecurity(this)
}
}
}
@ -253,6 +267,54 @@ class MainActivity : BaseActivity() {
}
}
// --> EH
//Lock code
var willLock = false
var disableLock = false
override fun onRestart() {
super.onRestart()
if(willLock && lockEnabled() && !disableLock) {
doLock()
}
willLock = false
}
override fun onStop() {
super.onStop()
tryLock()
}
fun tryLock() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val mUsageStatsManager = getSystemService("usagestats") as UsageStatsManager
val time = System.currentTimeMillis()
// We get usage stats for the last 20 seconds
val sortedStats =
mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
time - 1000 * 20,
time)
?.associateBy {
it.lastTimeUsed
}?.toSortedMap()
if(sortedStats != null && sortedStats.isNotEmpty())
if(sortedStats[sortedStats.lastKey()]?.packageName != packageName)
willLock = true
} else {
val am = getSystemService(Service.ACTIVITY_SERVICE) as ActivityManager
val running = am.getRunningTasks(1)[0]
if (running.topActivity.packageName != packageName) {
willLock = true
}
}
}
fun doLock() {
router.pushController(RouterTransaction.with(LockController())
.popChangeHandler(LockChangeHandler()))
}
// <-- EH
companion object {
// Shortcut actions
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"

View File

@ -1,19 +1,15 @@
package eu.kanade.tachiyomi.ui.setting
import android.content.Intent
import android.os.Bundle
import android.support.v7.preference.PreferenceScreen
import android.view.View
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import exh.ui.migration.MetadataFetchDialog
import exh.ui.login.LoginActivity
import uy.kohesive.injekt.injectLazy
/**
* EH Settings fragment
*/
class SettingsEhFragment : SettingsController() {
class SettingsEhController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
title = "E-Hentai"
@ -27,13 +23,24 @@ class SettingsEhFragment : SettingsController() {
.asObservable().subscribeUntilDestroy {
isChecked = it
}
onChange { newVal ->
newVal as Boolean
if(!newVal) {
preferences.enableExhentai().set(false)
true
} else {
startActivity(Intent(context, LoginActivity::class.java))
false
}
}
}
switchPreference {
title = "Use Hentai@Home Network"
summary = "Do you wish to load images through the Hentai@Home Network? Disabling this option will reduce the amount of pages you are able to view"
key = "enable_hah"
defaultValue = "true"
defaultValue = true
}
switchPreference {
@ -41,11 +48,11 @@ class SettingsEhFragment : SettingsController() {
summaryOn = "Currently showing Japanese titles in search results. Clear the chapter cache after changing this (in the Advanced section)"
summaryOff = "Currently showing English/Romanized titles in search results. Clear the chapter cache after changing this (in the Advanced section)"
key = "use_jp_title"
defaultValue = "false"
defaultValue = false
}
switchPreference {
defaultValue = "true"
defaultValue = true
key = "secure_exh"
title = "Secure ExHentai/E-Hentai"
summary = "Use the HTTPS version of ExHentai/E-Hentai."
@ -91,15 +98,13 @@ class SettingsEhFragment : SettingsController() {
"rc_2",
"rc_3"
)
dependency = "enable_exhentai"
}
}.dependency = "enable_exhentai"
listPreference {
defaultValue = "tr_2"
title = "Thumbnail rows"
summary = "Affects loading speeds. It is recommended to set this to the maximum size your hath perks allow"
key = "ex_thumb_rows"
dependency = "enable_exhentai"
entries = arrayOf(
"4",
"10 (requires 'More Thumbs' hath perk)",
@ -112,7 +117,7 @@ class SettingsEhFragment : SettingsController() {
"tr_10",
"tr_20"
)
}
}.dependency = "enable_exhentai"
preferenceCategory {
title = "Advanced"
@ -122,48 +127,14 @@ class SettingsEhFragment : SettingsController() {
title = "Migrate library metadata"
isPersistent = false
key = "ex_migrate_library"
summary = "Fetch the library metadata to enable tag searching in the library. This button will be visible even if you have already fetched the metadata" />
summary = "Fetch the library metadata to enable tag searching in the library. This button will be visible even if you have already fetched the metadata"
onClick {
activity?.let {
MetadataFetchDialog().askMigration(it)
}
}
}
}
}
private val preferences: PreferencesHelper by injectLazy()
val enableExhentaiPref by lazy {
findPreference("enable_exhentai") as SwitchPreference
}
val migrateLibraryPref by lazy {
findPreference("ex_migrate_library") as Preference
}
val useJpTitlePref by lazy {
findPreference("use_jp_title") as SwitchPreference
}
override fun onViewCreated(view: View, savedState: Bundle?) {
super.onViewCreated(view, savedState)
enableExhentaiPref.setOnPreferenceChangeListener { preference, newVal ->
newVal as Boolean
(activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_EH_RECREATE
if(!newVal) {
preferences.enableExhentai().set(false)
true
} else {
startActivity(Intent(context, LoginActivity::class.java))
false
}
}
migrateLibraryPref.setOnPreferenceClickListener {
MetadataFetchDialog().askMigration(activity)
true
}
useJpTitlePref.setOnPreferenceChangeListener { preference, any ->
(activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_EH_RECREATE
true
}
}
}

View File

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.LocaleHelper
import exh.ui.lock.LockPreference
import kotlinx.android.synthetic.main.pref_library_columns.view.*
import rx.Observable
import uy.kohesive.injekt.Injekt
@ -175,6 +176,13 @@ class SettingsGeneralController : SettingsController() {
true
}
}
LockPreference(context).apply {
key = "pref_app_lock"
title = "Application lock"
isPersistent = false
addPreference(this)
}
}
class LibraryColumnsDialog : DialogController() {

View File

@ -48,6 +48,12 @@ class SettingsMainController : SettingsController() {
titleRes = R.string.backup
onClick { navigateTo(SettingsBackupController()) }
}
preference {
iconRes = R.drawable.eh_ic_ehlogo_red_24dp
iconTint = tintColor
titleRes = R.string.pref_category_eh
onClick { navigateTo(SettingsEhController()) }
}
preference {
iconRes = R.drawable.ic_code_black_24dp
iconTint = tintColor

View File

@ -56,68 +56,75 @@ class GalleryAdder {
val outJson = JsonParser().parse(networkHelper.client.newCall(Request.Builder()
.url(API_BASE)
.post(RequestBody.create(JSON, json.toString()))
.build()).execute().body().string()).obj
.build()).execute().body()!!.string()).obj
val obj = outJson["tokenlist"].array.first()
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
}
fun addGallery(url: String, fav: Boolean = false): Manga {
val urlObj = Uri.parse(url)
val source = when(urlObj.host) {
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
"exhentai.org" -> EXH_SOURCE_ID
else -> throw MalformedURLException("Not a valid gallery URL!")
}
val realUrl = when (urlObj.pathSegments.first().toLowerCase()) {
"g" -> {
//Is already gallery page, do nothing
url
}
"s" -> {
//Is page, fetch gallery token and use that
getGalleryUrlFromPage(url)
}
else -> {
throw MalformedURLException("Not a valid gallery URL!")
}
}
val sourceObj = sourceManager.get(source)
?: throw IllegalStateException("Could not find EH source!")
val pathOnlyUrl = getUrlWithoutDomain(realUrl)
//Use manga in DB if possible, otherwise, make a new manga
val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking()
?: Manga.create(source).apply {
this.url = pathOnlyUrl
title = realUrl
}
//Copy basics
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
//Apply metadata
metadataHelper.fetchEhMetadata(realUrl, isExSource(source))?.copyTo(manga)
if(fav) manga.favorite = true
db.insertManga(manga).executeAsBlocking().insertedId()?.let {
manga.id = it
}
//Fetch and copy chapters
fun addGallery(url: String, fav: Boolean = false): GalleryAddEvent {
try {
sourceObj.fetchChapterList(manga).map {
syncChaptersWithSource(db, it, manga, sourceObj)
}.toBlocking().first()
} catch (e: Exception) {
Timber.w(e, "Failed to update chapters for gallery: ${manga.title}!")
}
val urlObj = Uri.parse(url)
val source = when (urlObj.host) {
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
"exhentai.org" -> EXH_SOURCE_ID
else -> return GalleryAddEvent.Fail.UnknownType(url)
}
return manga
val realUrl = when (urlObj.pathSegments.first().toLowerCase()) {
"g" -> {
//Is already gallery page, do nothing
url
}
"s" -> {
//Is page, fetch gallery token and use that
getGalleryUrlFromPage(url)
}
else -> {
return GalleryAddEvent.Fail.UnknownType(url)
}
}
val sourceObj = sourceManager.get(source)
?: return GalleryAddEvent.Fail.Error(url, "Could not find EH source!")
val pathOnlyUrl = getUrlWithoutDomain(realUrl)
//Use manga in DB if possible, otherwise, make a new manga
val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking()
?: Manga.create(source).apply {
this.url = pathOnlyUrl
title = realUrl
}
//Copy basics
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
//Apply metadata
metadataHelper.fetchEhMetadata(realUrl, isExSource(source))?.copyTo(manga)
if (fav) manga.favorite = true
db.insertManga(manga).executeAsBlocking().insertedId()?.let {
manga.id = it
}
//Fetch and copy chapters
try {
sourceObj.fetchChapterList(manga).map {
syncChaptersWithSource(db, it, manga, sourceObj)
}.toBlocking().first()
} catch (e: Exception) {
Timber.e(e, "Failed to update chapters for gallery: ${manga.title}!")
return GalleryAddEvent.Fail.Error(url, "Failed to update chapters for gallery: $url")
}
return GalleryAddEvent.Success(url, manga)
} catch(e: Exception) {
Timber.e(e, "Could not add gallery!")
return GalleryAddEvent.Fail.Error(url,
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim())
}
}
private fun getUrlWithoutDomain(orig: String): String {
@ -133,4 +140,28 @@ class GalleryAdder {
return orig
}
}
}
sealed class GalleryAddEvent {
abstract val logMessage: String
abstract val galleryUrl: String
open val galleryTitle: String? = null
class Success(override val galleryUrl: String,
val manga: Manga): GalleryAddEvent() {
override val logMessage = "[OK] Added gallery: $galleryTitle"
override val galleryTitle: String
get() = manga.title
}
sealed class Fail: GalleryAddEvent() {
class UnknownType(override val galleryUrl: String): Fail() {
override val logMessage = "[ERROR] Unknown gallery type for gallery: $galleryUrl"
}
class Error(override val galleryUrl: String,
val message: String): Fail() {
override val logMessage = "[ERROR] $message"
}
}
}

View File

@ -1,131 +1,145 @@
package exh.ui.batchadd
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.afollestad.materialdialogs.MaterialDialog
import com.jakewharton.rxbinding.view.clicks
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
import exh.GalleryAdder
import exh.metadata.nullIfBlank
import kotlinx.android.synthetic.main.eh_fragment_batch_add.*
import timber.log.Timber
import kotlin.concurrent.thread
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.combineLatest
import eu.kanade.tachiyomi.util.plusAssign
import kotlinx.android.synthetic.main.eh_fragment_batch_add.view.*
import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription
/**
* LoginActivity
* Batch add screen
*/
class BatchAddController : NucleusController<BatchAddPresenter>() {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup) =
inflater.inflate(R.layout.eh_fragment_batch_add, container, false)!!
class BatchAddFragment : BaseFragment() {
override fun getTitle() = "Batch add"
private val galleryAdder by lazy { GalleryAdder() }
override fun createPresenter() = BatchAddPresenter()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?)
= inflater.inflate(R.layout.eh_fragment_batch_add, container, false)!!
override fun onViewCreated(view: View, savedViewState: Bundle?) {
super.onViewCreated(view, savedViewState)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
setToolbarTitle("Batch add")
setup()
}
fun setup() {
btn_add_galleries.setOnClickListener {
val galleries = galleries_box.text.toString()
//Check text box has content
if(galleries.isNullOrBlank()) {
noGalleriesSpecified()
return@setOnClickListener
with(view) {
btn_add_galleries.clicks().subscribeUntilDestroy {
addGalleries(galleries_box.text.toString())
}
//Too lazy to actually deal with orientation changes
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
progress_dismiss_btn.clicks().subscribeUntilDestroy {
presenter.currentlyAddingRelay.call(false)
}
val splitGalleries = galleries.split("\n").map {
it.trim().nullIfBlank()
}.filterNotNull()
val progressSubscriptions = CompositeSubscription()
val dialog = MaterialDialog.Builder(context)
.title("Adding galleries...")
.progress(false, splitGalleries.size, true)
.cancelable(false)
.canceledOnTouchOutside(false)
presenter.currentlyAddingRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
progressSubscriptions.clear()
if(it) {
showProgress(this)
progressSubscriptions += presenter.progressRelay
.observeOn(AndroidSchedulers.mainThread())
.combineLatest(presenter.progressTotalRelay, { progress, total ->
//Show hide dismiss button
progress_dismiss_btn.visibility =
if(progress == total)
View.VISIBLE
else View.GONE
formatProgress(progress, total)
}).subscribeUntilDestroy {
progress_text.text = it
}
progressSubscriptions += presenter.progressTotalRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
progress_bar.max = it
}
progressSubscriptions += presenter.progressRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
progress_bar.progress = it
}
presenter.eventRelay
?.observeOn(AndroidSchedulers.mainThread())
?.subscribeUntilDestroy {
progress_log.append("$it\n")
}?.let {
progressSubscriptions += it
}
} else hideProgress(this)
}
}
}
private val View.progressViews
get() = listOf(
progress_title_view,
progress_log_wrapper,
progress_bar,
progress_text,
progress_dismiss_btn
)
private val View.inputViews
get() = listOf(
input_title_view,
galleries_box,
btn_add_galleries
)
private var List<View>.visibility: Int
get() = throw UnsupportedOperationException()
set(v) { forEach { it.visibility = v } }
private fun showProgress(target: View? = view) {
target?.apply {
progressViews.visibility = View.VISIBLE
inputViews.visibility = View.GONE
}?.progress_log?.text = ""
}
private fun hideProgress(target: View? = view) {
target?.apply {
progressViews.visibility = View.GONE
inputViews.visibility = View.VISIBLE
}?.galleries_box?.setText("", TextView.BufferType.EDITABLE)
}
private fun formatProgress(progress: Int, total: Int) = "$progress/$total"
private fun addGalleries(galleries: String) {
//Check text box has content
if(galleries.isBlank()) {
noGalleriesSpecified()
return
}
presenter.addGalleries(galleries)
}
private fun noGalleriesSpecified() {
activity?.let {
MaterialDialog.Builder(it)
.title("No galleries to add!")
.content("You must specify at least one gallery to add!")
.positiveText("Ok")
.onPositive { materialDialog, _ -> materialDialog.dismiss() }
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
val succeeded = mutableListOf<String>()
val failed = mutableListOf<String>()
thread {
splitGalleries.forEachIndexed { i, s ->
activity.runOnUiThread {
dialog.setContent("Processing: $s")
}
if(addGallery(s)) {
succeeded.add(s)
} else {
failed.add(s)
}
activity.runOnUiThread {
dialog.setProgress(i + 1)
}
}
//Show report
val succeededCount = succeeded.size
val failedCount = failed.size
if(succeeded.isEmpty()) succeeded += "None"
if(failed.isEmpty()) failed += "None"
val succeededReport = succeeded.joinToString(separator = "\n", prefix = "Added:\n")
val failedReport = failed.joinToString(separator = "\n", prefix = "Failed:\n")
val summary = "Summary:\nAdded: $succeededCount gallerie(s)\nFailed: $failedCount gallerie(s)"
val report = listOf(succeededReport, failedReport, summary).joinToString(separator = "\n\n")
activity.runOnUiThread {
//Enable orientation changes again
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
dialog.dismiss()
MaterialDialog.Builder(context)
.title("Batch add report")
.content(report)
.positiveText("Ok")
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
}
}
}
}
fun addGallery(url: String): Boolean {
try {
galleryAdder.addGallery(url, true)
} catch(t: Throwable) {
Timber.e(t, "Could not add gallery!")
return false
}
return true
}
fun noGalleriesSpecified() {
MaterialDialog.Builder(context)
.title("No galleries to add!")
.content("You must specify at least one gallery to add!")
.positiveText("Ok")
.onPositive { materialDialog, _ -> materialDialog.dismiss() }
.cancelable(true)
.canceledOnTouchOutside(true)
.show()
}
companion object {
fun newInstance() = BatchAddFragment()
}
}

View File

@ -1,5 +1,51 @@
package exh.ui.batchadd
/**
* Created by nulldev on 8/23/17.
*/
import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.ReplayRelay
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import exh.GalleryAddEvent
import exh.GalleryAdder
import exh.metadata.nullIfBlank
import kotlin.concurrent.thread
class BatchAddPresenter: BasePresenter<BatchAddController>() {
private val galleryAdder by lazy { GalleryAdder() }
val progressTotalRelay = BehaviorRelay.create(0)!!
val progressRelay = BehaviorRelay.create(0)!!
var eventRelay: ReplayRelay<String>? = null
val currentlyAddingRelay = BehaviorRelay.create(false)!!
fun addGalleries(galleries: String) {
eventRelay = ReplayRelay.create()
val splitGalleries = galleries.split("\n").map {
it.trim().nullIfBlank()
}.filterNotNull()
progressRelay.call(0)
progressTotalRelay.call(splitGalleries.size)
currentlyAddingRelay.call(true)
thread {
val succeeded = mutableListOf<String>()
val failed = mutableListOf<String>()
splitGalleries.forEachIndexed { i, s ->
val result = galleryAdder.addGallery(s, true)
if(result is GalleryAddEvent.Success) {
succeeded.add(s)
} else {
failed.add(s)
}
progressRelay.call(i + 1)
eventRelay?.call(result.logMessage)
}
//Show report
val summary = "\nSummary:\nAdded: ${succeeded.size} gallerie(s)\nFailed: ${failed.size} gallerie(s)"
eventRelay?.call(summary)
}
}
}

View File

@ -6,12 +6,11 @@ import android.view.MenuItem
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.manga.MangaActivity
import exh.GalleryAdder
import kotlinx.android.synthetic.main.toolbar.*
import timber.log.Timber
import kotlin.concurrent.thread
//TODO :(
class InterceptActivity : BaseActivity() {
private val galleryAdder = GalleryAdder()
@ -19,12 +18,9 @@ class InterceptActivity : BaseActivity() {
var finished = false
override fun onCreate(savedInstanceState: Bundle?) {
setAppTheme()
super.onCreate(savedInstanceState)
setContentView(R.layout.eh_activity_intercept)
setupToolbar(toolbar, backNavigation = false)
if(savedInstanceState == null)
thread { setup() }
}
@ -54,8 +50,9 @@ class InterceptActivity : BaseActivity() {
if(Intent.ACTION_VIEW == intent.action) {
val manga = galleryAdder.addGallery(intent.dataString)
if(!finished)
startActivity(MangaActivity.newIntent(this, manga, true))
//TODO
// if(!finished)
// startActivity(MangaActivity.newIntent(this, manga, true))
onBackPressed()
}
}

View File

@ -1,60 +0,0 @@
package exh.ui.lock
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.andrognito.pinlockview.PinLockListener
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import kotlinx.android.synthetic.main.activity_lock.*
import uy.kohesive.injekt.injectLazy
class LockActivity : BaseActivity() {
val prefs: PreferencesHelper by injectLazy()
override fun onCreate(savedInstanceState: Bundle?) {
disableLock = true
setTheme(R.style.Theme_Tachiyomi_Dark)
super.onCreate(savedInstanceState)
if(!lockEnabled(prefs)) {
finish()
return
}
setContentView(R.layout.activity_lock)
pin_lock_view.attachIndicatorDots(indicator_dots)
pin_lock_view.pinLength = prefs.lockLength().getOrDefault()
pin_lock_view.setPinLockListener(object : PinLockListener {
override fun onEmpty() {}
override fun onComplete(pin: String) {
if(sha512(pin, prefs.lockSalt().get()!!) == prefs.lockHash().get()) {
//Yay!
finish()
} else {
MaterialDialog.Builder(this@LockActivity)
.title("PIN code incorrect")
.content("The PIN code you entered is incorrect. Please try again.")
.cancelable(true)
.canceledOnTouchOutside(true)
.positiveText("Ok")
.autoDismiss(true)
.show()
pin_lock_view.resetPinLockView()
}
}
override fun onPinChange(pinLength: Int, intermediatePin: String?) {}
})
}
override fun onBackPressed() {
moveTaskToBack(true)
}
}

View File

@ -0,0 +1,41 @@
package exh.ui.lock
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
import java.util.ArrayList
class LockChangeHandler : AnimatorChangeHandler {
constructor(): super()
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
constructor(duration: Long) : super(duration)
constructor(duration: Long, removesFromViewOnPush: Boolean) : super(duration, removesFromViewOnPush)
override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator {
val animator = AnimatorSet()
val viewAnimators = ArrayList<Animator>()
if (!isPush && from != null) {
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_X, 5f))
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_Y, 5f))
viewAnimators.add(ObjectAnimator.ofFloat(from, View.ALPHA, 0f))
}
animator.playTogether(viewAnimators)
return animator
}
override fun resetFromView(from: View) {}
override fun copy(): ControllerChangeHandler =
LockChangeHandler(animationDuration, removesFromViewOnPush())
}

View File

@ -0,0 +1,68 @@
package exh.ui.lock
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog
import com.andrognito.pinlockview.PinLockListener
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import kotlinx.android.synthetic.main.activity_lock.view.*
import uy.kohesive.injekt.injectLazy
class LockController : NucleusController<LockPresenter>() {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup)
= inflater.inflate(R.layout.activity_lock, container, false)!!
override fun createPresenter() = LockPresenter()
override fun getTitle() = "Application locked"
val prefs: PreferencesHelper by injectLazy()
override fun onViewCreated(view: View, savedViewState: Bundle?) {
super.onViewCreated(view, savedViewState)
if(!lockEnabled(prefs)) {
closeLock()
return
}
with(view) {
pin_lock_view.attachIndicatorDots(indicator_dots)
pin_lock_view.pinLength = prefs.lockLength().getOrDefault()
pin_lock_view.setPinLockListener(object : PinLockListener {
override fun onEmpty() {}
override fun onComplete(pin: String) {
if (sha512(pin, prefs.lockSalt().get()!!) == prefs.lockHash().get()) {
//Yay!
closeLock()
} else {
MaterialDialog.Builder(context)
.title("PIN code incorrect")
.content("The PIN code you entered is incorrect. Please try again.")
.cancelable(true)
.canceledOnTouchOutside(true)
.positiveText("Ok")
.autoDismiss(true)
.show()
pin_lock_view.resetPinLockView()
}
}
override fun onPinChange(pinLength: Int, intermediatePin: String?) {}
})
}
}
fun closeLock() {
router.popCurrentController()
}
override fun handleBack() = true
}

View File

@ -17,7 +17,7 @@ import java.security.SecureRandom
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Preference(context, attrs) {
val secureRandom by lazy { SecureRandom() }
private val secureRandom by lazy { SecureRandom() }
val prefs: PreferencesHelper by injectLazy()
@ -26,12 +26,11 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
updateSummary()
}
fun updateSummary() {
if(lockEnabled(prefs)) {
summary = "Application is locked"
} else {
summary = "Application is not locked, tap to lock"
}
private fun updateSummary() {
summary = if(lockEnabled(prefs))
"Application is locked"
else
"Application is not locked, tap to lock"
}
override fun onClick() {
@ -65,7 +64,7 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
}
}
fun savePassword(password: String) {
private fun savePassword(password: String) {
val salt: String?
val hash: String?
val length: Int

View File

@ -0,0 +1,6 @@
package exh.ui.lock
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
class LockPresenter: BasePresenter<LockController>()

View File

@ -44,13 +44,6 @@ fun lockEnabled(prefs: PreferencesHelper = Injekt.get())
&& prefs.lockSalt().get() != null
&& prefs.lockLength().getOrDefault() != -1
/**
* Lock the screen
*/
fun showLockActivity(activity: Activity) {
activity.startActivity(Intent(activity, LockActivity::class.java))
}
/**
* Check if the lock will function properly
*

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import exh.EXH_SOURCE_ID
import kotlinx.android.synthetic.main.eh_activity_login.*
import kotlinx.android.synthetic.main.toolbar.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
@ -34,13 +33,10 @@ class LoginActivity : BaseActivity() {
val sourceManager: SourceManager by injectLazy()
override fun onCreate(savedInstanceState: Bundle?) {
setAppTheme()
super.onCreate(savedInstanceState)
setContentView(R.layout.eh_activity_login)
setup()
setupToolbar(toolbar, backNavigation = false)
}
fun setup() {
@ -187,7 +183,7 @@ class LoginActivity : BaseActivity() {
document.getElementsByName('submit')[0].style.visibility = 'visible';
document.querySelector('td[width="60%"][valign="top"]').style.visibility = 'visible';
function hide(e) {if(e !== null && e !== undefined) e.style.display = 'none';}
function hide(e) {if(e != null) e.style.display = 'none';}
hide(document.querySelector(".errorwrap"));
hide(document.querySelector('td[width="40%"][valign="top"]'));
@ -202,7 +198,7 @@ class LoginActivity : BaseActivity() {
hide(fd[2]);
hide(child.querySelector('br'));
var error = document.querySelector(".page > div > .borderwrap");
if(error !== null) error.style.visibility = 'visible';
if(error != null) error.style.visibility = 'visible';
hide(fh[0]);
hide(fh[1]);
hide(document.querySelector("#gfooter"));
@ -211,7 +207,7 @@ class LoginActivity : BaseActivity() {
e.style.color = "white";
});
var pc = document.querySelector(".postcolor");
if(pc !== null) pc.style.color = "#26353F";
if(pc != null) pc.style.color = "#26353F";
})()
"""
}

View File

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/backgroundDark">
<com.andrognito.pinlockview.PinLockView
android:id="@+id/pin_lock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/indicator_dots"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/indicator_dots" />
<com.andrognito.pinlockview.IndicatorDots
android:id="@+id/indicator_dots"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/pin_lock_view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/pin_lock_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
</android.support.constraint.ConstraintLayout>

View File

@ -17,8 +17,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/toolbar"/>
</android.support.design.widget.AppBarLayout>
<FrameLayout

View File

@ -17,8 +17,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/toolbar"/>
</android.support.design.widget.AppBarLayout>
<android.support.constraint.ConstraintLayout

View File

@ -9,46 +9,121 @@
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:padding="16dp ">
<TextView
android:text="Enter the galleries to add (E-Hentai/ExHentai only, separated by a new line):"
android:id="@+id/input_title_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:text="Enter the galleries to add (separated by a new line):"
android:textAppearance="@style/TextAppearance.Medium.Title"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp" />
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/galleries_box"
android:layout_width="0dp"
android:layout_height="0dp"
android:inputType="textMultiLine"
android:ems="10"
android:id="@+id/galleries_box"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:layout_constraintLeft_toLeftOf="@+id/textView"
app:layout_constraintRight_toRightOf="@+id/textView"
android:hint="Example:\n\nhttp://e-hentai.org/g/12345/1a2b3c4e\nhttp://g.e-hentai.org/g/67890/6f7g8h9i\nhttp://exhentai.org/g/13579/1a3b5c7e\nhttps://exhentai.org/g/24680/2f4g6h8i\n"
android:inputType="textMultiLine"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/btn_add_galleries"
android:layout_marginBottom="8dp"
android:hint="Example:\n\nhttp://e-hentai.org/g/12345/1a2b3c4e\nhttp://g.e-hentai.org/g/67890/6f7g8h9i\nhttp://exhentai.org/g/13579/1a3b5c7e\nhttps://exhentai.org/g/24680/2f4g6h8i\n" />
app:layout_constraintLeft_toLeftOf="@+id/input_title_view"
app:layout_constraintRight_toRightOf="@+id/input_title_view"
app:layout_constraintTop_toBottomOf="@+id/input_title_view" />
<Button
android:text="Add Galleries"
android:id="@+id/btn_add_galleries"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/btn_add_galleries"
android:text="Add Galleries"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintLeft_toLeftOf="@+id/galleries_box"
app:layout_constraintRight_toRightOf="@+id/galleries_box" />
<TextView
android:id="@+id/progress_title_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
android:text="Adding galleries..."
android:textAppearance="@style/TextAppearance.Medium.Title"
android:visibility="visible"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/progress_dismiss_btn"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper"
app:layout_constraintRight_toLeftOf="@+id/progress_text" />
<TextView
android:id="@+id/progress_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:singleLine="true"
android:text=""
android:textAlignment="textEnd"
android:visibility="visible"
app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper"
app:layout_constraintTop_toTopOf="@+id/progress_bar" />
<Button
android:id="@+id/progress_dismiss_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:text="Finish"
android:visibility="visible"
app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper"
app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintBottom_toBottomOf="@+id/btn_add_galleries"
android:layout_marginBottom="0dp" />
<ScrollView
android:id="@+id/progress_log_wrapper"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/progress_bar"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="@+id/progress_title_view"
app:layout_constraintRight_toRightOf="@+id/progress_title_view"
app:layout_constraintTop_toBottomOf="@+id/progress_title_view"
app:layout_constraintVertical_bias="0.0">
<TextView
android:id="@+id/progress_log"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Regular.Body1"
android:visibility="visible" />
</ScrollView>
</android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -101,48 +101,4 @@
<item>1</item>
<item>2</item>
</string-array>
<!-- EH -->
<string-array name="ehentai_quality">
<item>Auto</item>
<item>2400x</item>
<item>1600x</item>
<item>1280x</item>
<item>980x</item>
<item>780x</item>
</string-array>
<string-array name="ehentai_quality_values">
<item>auto</item>
<item>ovrs_2400</item>
<item>ovrs_1600</item>
<item>high</item>
<item>med</item>
<item>low</item>
</string-array>
<string-array name="ehentai_search_result_count">
<item>25 results</item>
<item>50 results</item>
<item>100 results</item>
<item>200 results</item>
</string-array>
<string-array name="ehentai_search_result_count_values">
<item>rc_0</item>
<item>rc_1</item>
<item>rc_2</item>
<item>rc_3</item>
</string-array>
<string-array name="ehentai_thumbnail_rows">
<item>4</item>
<item>10 (requires \'More Thumbs\' hath perk)</item>
<item>20 (requires \'Thumbs Up\' hath perk)</item>
<item>40 (requires \'All Thumbs\' hath perk)</item>
</string-array>
<string-array name="ehentai_thumbnail_rows_values">
<item>tr_2</item>
<item>tr_5</item>
<item>tr_10</item>
<item>tr_20</item>
</string-array>
</resources>

View File

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.manifest.AndroidManifest
//import org.robolectric.RobolectricTestRunner
//import org.robolectric.annotation.Config
//import org.robolectric.manifest.AndroidManifest
class CustomRobolectricGradleTestRunner(klass: Class<*>) : RobolectricTestRunner(klass) {
//class CustomRobolectricGradleTestRunner(klass: Class<*>) : RobolectricTestRunner(klass) {
override fun getAppManifest(config: Config): AndroidManifest {
return super.getAppManifest(config).apply { packageName = "eu.kanade.tachiyomi" }
}
}
// override fun getAppManifest(config: Config): AndroidManifest {
// return super.getAppManifest(config).apply { packageName = "eu.kanade.tachiyomi" }
// }
//}