diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bc1e14c52..72faea00a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -105,6 +105,34 @@
android:parentActivityName=".ui.setting.SettingsActivity" >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
index bd2371111..e372b4fed 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
@@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryFragment
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
+import exh.ui.batchadd.BatchAddFragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.*
import uy.kohesive.injekt.injectLazy
@@ -63,6 +64,7 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id)
R.id.nav_drawer_latest_updates -> setFragment(LatestUpdatesFragment.newInstance(), id)
+ R.id.nav_drawer_batch_add -> setFragment(BatchAddFragment.newInstance(), id)
R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance(), id)
R.id.nav_drawer_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt
new file mode 100644
index 000000000..acb2557ab
--- /dev/null
+++ b/app/src/main/java/exh/GalleryAdder.kt
@@ -0,0 +1,54 @@
+package exh
+
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.source.SourceManager
+import eu.kanade.tachiyomi.util.UrlUtil
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import uy.kohesive.injekt.injectLazy
+import java.net.MalformedURLException
+import java.net.URL
+
+class GalleryAdder {
+
+ private val db: DatabaseHelper by injectLazy()
+
+ private val sourceManager: SourceManager by injectLazy()
+
+ private val metadataHelper = MetadataHelper()
+
+ fun addGallery(url: String, fav: Boolean = false): Manga {
+ val source = when(URL(url).host) {
+ "g.e-hentai.org" -> 1
+ "exhentai.org" -> 2
+ else -> throw MalformedURLException("Not a valid gallery URL!")
+ }
+
+ val sourceObj = sourceManager.get(source)
+
+ val pathOnlyUrl = UrlUtil.getPath(url)
+
+ //Use manga in DB if possible, otherwise, make a new manga
+ val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking()
+ ?: Manga.create(pathOnlyUrl, source).apply {
+ title = url
+ }
+
+ sourceObj?.let {
+ //Copy basics
+ manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
+
+ //Apply metadata
+ metadataHelper.fetchMetadata(url, source == 2)?.copyTo(manga)
+ }
+
+ if(fav) manga.favorite = true
+
+ db.insertManga(manga).executeAsBlocking().insertedId()?.let {
+ manga.id = it
+ }
+
+ return manga
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt b/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt
new file mode 100644
index 000000000..5e9cecdb9
--- /dev/null
+++ b/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt
@@ -0,0 +1,126 @@
+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 com.afollestad.materialdialogs.MaterialDialog
+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
+
+/**
+ * LoginActivity
+ */
+
+class BatchAddFragment : BaseFragment() {
+
+ private val galleryAdder by lazy { GalleryAdder() }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?)
+ = inflater.inflate(R.layout.eh_fragment_batch_add, container, false)!!
+
+ 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()
+
+ //Too lazy to actually deal with orientation changes
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
+
+ val splitGalleries = galleries.split("\n").map {
+ it.trim().nullIfBlank()
+ }.filterNotNull()
+
+ val dialog = MaterialDialog.Builder(context)
+ .title("Adding galleries...")
+ .progress(false, splitGalleries.size, true)
+ .cancelable(false)
+ .canceledOnTouchOutside(false)
+ .show()
+
+ val succeeded = mutableListOf()
+ val failed = mutableListOf()
+
+ 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
+ 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: ${succeeded.size} gallerie(s)\nFailed: ${failed.size} 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, dialogAction -> materialDialog.dismiss() }
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+
+ companion object {
+ fun newInstance() = BatchAddFragment()
+ }
+}
diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt
new file mode 100644
index 000000000..fa1a7e605
--- /dev/null
+++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt
@@ -0,0 +1,82 @@
+package exh.ui.intercept
+
+import android.content.Intent
+import android.os.Bundle
+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
+
+class InterceptActivity : BaseActivity() {
+
+ private val galleryAdder = GalleryAdder()
+
+ 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() }
+ }
+
+ fun setup() {
+ try {
+ processLink()
+ } catch(t: Throwable) {
+ Timber.e(t, "Could not intercept link!")
+ if(!finished)
+ runOnUiThread {
+ MaterialDialog.Builder(this)
+ .title("Error")
+ .content("Could not load this gallery!")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .cancelListener { onBackPressed() }
+ .positiveText("Ok")
+ .onPositive { materialDialog, dialogAction -> onBackPressed() }
+ .dismissListener { onBackPressed() }
+ .show()
+ }
+ }
+ }
+
+ fun processLink() {
+ if(Intent.ACTION_VIEW == intent.action) {
+ val manga = galleryAdder.addGallery(intent.dataString)
+
+ if(!finished)
+ startActivity(MangaActivity.newIntent(this, manga, true))
+ onBackPressed()
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> onBackPressed()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onBackPressed() {
+ if(!finished)
+ runOnUiThread {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finished = true
+ }
+}
diff --git a/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml b/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml
new file mode 100644
index 000000000..0460472b9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/eh_activity_intercept.xml b/app/src/main/res/layout/eh_activity_intercept.xml
new file mode 100644
index 000000000..518bb973e
--- /dev/null
+++ b/app/src/main/res/layout/eh_activity_intercept.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/eh_fragment_batch_add.xml b/app/src/main/res/layout/eh_fragment_batch_add.xml
new file mode 100644
index 000000000..c92600374
--- /dev/null
+++ b/app/src/main/res/layout/eh_fragment_batch_add.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml
index a461a5f36..c5b59c177 100644
--- a/app/src/main/res/menu/menu_navigation.xml
+++ b/app/src/main/res/menu/menu_navigation.xml
@@ -23,6 +23,10 @@
android:id="@+id/nav_drawer_latest_updates"
android:icon="@drawable/ic_watch_later_black_24dp"
android:title="@string/label_latest_updates" />
+