diff --git a/src/en/manta/AndroidManifest.xml b/src/en/manta/AndroidManifest.xml
new file mode 100644
index 000000000..30deb7f79
--- /dev/null
+++ b/src/en/manta/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/en/manta/build.gradle b/src/en/manta/build.gradle
new file mode 100644
index 000000000..bf78b241a
--- /dev/null
+++ b/src/en/manta/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Manta Comics'
+ pkgNameSuffix = 'en.manta'
+ extClass = '.MantaComics'
+ extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/manta/res/mipmap-hdpi/ic_launcher.png b/src/en/manta/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..1df104fef
Binary files /dev/null and b/src/en/manta/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-mdpi/ic_launcher.png b/src/en/manta/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..a17a00850
Binary files /dev/null and b/src/en/manta/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..72b9579f3
Binary files /dev/null and b/src/en/manta/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b2771784a
Binary files /dev/null and b/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..e05b2b87d
Binary files /dev/null and b/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/web_hi_res_512.png b/src/en/manta/res/web_hi_res_512.png
new file mode 100644
index 000000000..c3b3e63b5
Binary files /dev/null and b/src/en/manta/res/web_hi_res_512.png differ
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt
new file mode 100644
index 000000000..e8b8bb927
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt
@@ -0,0 +1,127 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import kotlinx.serialization.Serializable
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.TimeUnit.MILLISECONDS as MS
+
+private val isoDate by lazy {
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT)
+}
+
+private inline val String?.timestamp: Long
+ get() = this?.substringBefore('.')?.let(isoDate::parse)?.time ?: 0L
+
+@Serializable
+data class Series(
+ val data: T,
+ val id: Int,
+ val image: Cover,
+ val episodes: List? = null
+) {
+ override fun toString() = data.toString()
+}
+
+@Serializable
+data class Title(private val title: Name) {
+ override fun toString() = title.toString()
+}
+
+@Serializable
+data class Details(
+ val tags: List,
+ val isCompleted: Boolean? = null,
+ private val description: Description,
+ private val creators: List
+) {
+ val artists by lazy {
+ creators.filter { it.role == "Illustration" }
+ }
+
+ val authors by lazy {
+ creators.filter { it.role != "Illustration" }.ifEmpty { creators }
+ }
+
+ override fun toString() = description.toString()
+}
+
+@Serializable
+data class Episode(
+ val id: Int,
+ val ord: Int,
+ val data: Data?,
+ private val createdAt: String,
+ val cutImages: List? = null
+) {
+ val timestamp: Long
+ get() = createdAt.timestamp
+
+ val isLocked: Boolean
+ get() = timeTillFree > 0
+
+ val waitingTime: String
+ get() = when (val days = MS.toDays(timeTillFree)) {
+ 0L -> "later today"
+ 1L -> "tomorrow"
+ else -> "in $days days"
+ }
+
+ private val timeTillFree by lazy {
+ data?.freeAt.timestamp - System.currentTimeMillis()
+ }
+
+ override fun toString() = buildString {
+ append(data?.title ?: "Episode $ord")
+ if (isLocked) append(" \uD83D\uDD12")
+ }
+}
+
+@Serializable
+data class Data(
+ val title: String? = null,
+ val freeAt: String? = null
+)
+
+@Serializable
+data class Creator(
+ private val name: String,
+ val role: String
+) {
+ override fun toString() = name
+}
+
+@Serializable
+data class Description(
+ private val long: String,
+ private val short: String
+) {
+ override fun toString() = "$short\n\n$long"
+}
+
+@Serializable
+data class Cover(private val `1280x1840_480`: Image) {
+ override fun toString() = `1280x1840_480`.toString()
+}
+
+@Serializable
+data class Image(private val downloadUrl: String) {
+ override fun toString() = downloadUrl
+}
+
+@Serializable
+data class Tag(private val name: Name) {
+ override fun toString() = name.toString()
+}
+
+@Serializable
+data class Name(private val en: String) {
+ override fun toString() = en
+}
+
+@Serializable
+data class Status(
+ private val description: String,
+ private val message: String
+) {
+ override fun toString() = "$description: $message"
+}
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt
new file mode 100644
index 000000000..83e1cb846
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt
@@ -0,0 +1,128 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservable
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.HttpSource
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromJsonElement
+import kotlinx.serialization.json.jsonObject
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.injectLazy
+
+class MantaComics : HttpSource() {
+ override val name = "Manta"
+
+ override val lang = "en"
+
+ override val baseUrl = "https://manta.net"
+
+ override val supportsLatest = false
+
+ private val json by injectLazy()
+
+ override fun headersBuilder() = super.headersBuilder()
+ .set("User-Agent", "Manta/167").set("Origin", baseUrl)
+
+ override fun latestUpdatesRequest(page: Int) =
+ GET("$baseUrl/manta/v1/search/series?cat=New", headers)
+
+ override fun fetchPopularManga(page: Int) =
+ latestUpdatesRequest(page).fetch(::searchMangaParse)
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
+ filters.category.ifEmpty { if (query.isEmpty()) "New" else "" }.let {
+ GET("$baseUrl/manta/v1/search/series?cat=$it&q=$query", headers)
+ }
+
+ override fun searchMangaParse(response: Response) =
+ response.parse>>().map {
+ SManga.create().apply {
+ title = it.toString()
+ url = it.id.toString()
+ thumbnail_url = it.image.toString()
+ }
+ }.let { MangasPage(it, false) }
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
+ searchMangaRequest(page, query, filters).fetch(::searchMangaParse)
+
+ // Request the actual manga URL for the webview
+ override fun mangaDetailsRequest(manga: SManga) =
+ GET("$baseUrl/series/${manga.url}")
+
+ override fun mangaDetailsParse(response: Response) =
+ SManga.create().apply {
+ val data = response.parse>().data
+ description = data.toString()
+ genre = data.tags.joinToString()
+ artist = data.artists.joinToString()
+ author = data.authors.joinToString()
+ status = when (data.isCompleted) {
+ true -> SManga.COMPLETED
+ else -> SManga.ONGOING
+ }
+ initialized = true
+ }
+
+ override fun fetchMangaDetails(manga: SManga) =
+ chapterListRequest(manga).fetch(::mangaDetailsParse)
+
+ override fun chapterListRequest(manga: SManga) =
+ GET("$baseUrl/front/v1/series/${manga.url}", headers)
+
+ override fun chapterListParse(response: Response) =
+ response.parse>().episodes!!.map {
+ SChapter.create().apply {
+ name = it.toString()
+ url = it.id.toString()
+ date_upload = it.timestamp
+ chapter_number = it.ord.toFloat()
+ }
+ }
+
+ override fun fetchChapterList(manga: SManga) =
+ chapterListRequest(manga).fetch(::chapterListParse)
+
+ override fun pageListRequest(chapter: SChapter) =
+ GET("$baseUrl/front/v1/episodes/${chapter.url}", headers)
+
+ override fun pageListParse(response: Response) =
+ response.parse().run {
+ if (!isLocked) return@run cutImages!!
+ error("This episode will be available $waitingTime.")
+ }.mapIndexed { idx, img -> Page(idx, "", img.toString()) }
+
+ override fun fetchPageList(chapter: SChapter) =
+ pageListRequest(chapter).fetch(::pageListParse)
+
+ override fun getFilterList() = FilterList(Category())
+
+ override fun latestUpdatesParse(response: Response) =
+ throw UnsupportedOperationException("Not used")
+
+ override fun popularMangaRequest(page: Int) =
+ throw UnsupportedOperationException("Not used")
+
+ override fun popularMangaParse(response: Response) =
+ throw UnsupportedOperationException("Not used")
+
+ override fun imageUrlParse(response: Response) =
+ throw UnsupportedOperationException("Not used")
+
+ private fun Request.fetch(parse: (Response) -> R) =
+ client.newCall(this).asObservable().map { res ->
+ if (res.isSuccessful) return@map parse(res)
+ error(res.parse("status").toString())
+ }!!
+
+ private inline fun Response.parse(key: String = "data") =
+ json.decodeFromJsonElement(
+ json.parseToJsonElement(body!!.string()).jsonObject[key]!!
+ )
+}
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt
new file mode 100644
index 000000000..504803764
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt
@@ -0,0 +1,23 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+private val categories = arrayOf(
+ "",
+ "New",
+ "Exclusive",
+ "Completed",
+ "Romance",
+ "BL / GL",
+ "Drama",
+ "Fantasy",
+ "Thriller",
+ "Slice of life"
+)
+
+class Category(
+ values: Array = categories
+) : Filter.Select("Category", values)
+
+inline val List>.category: String
+ get() = (firstOrNull() as? Category)?.run { values[state] } ?: ""