Compare commits

...

13 Commits
v0.5 ... v0.5.2

Author SHA1 Message Date
Koitharu
0a4f2f848e UI fixes 2020-09-20 17:31:33 +03:00
Koitharu
85fc3a024c Fix henchan 2020-09-20 17:08:42 +03:00
Koitharu
eeb536b1ac Fix crash on import 2020-09-19 17:32:22 +03:00
Koitharu
5b8e8d76c0 Update target sdk 2020-09-19 17:27:45 +03:00
Koitharu
73cf2964b2 Option to manually track manga updates 2020-09-19 15:22:18 +03:00
Koitharu
8372f9b5de Update dependencies 2020-09-19 14:21:32 +03:00
Koitharu
d7181e35e7 Update dependencies 2020-09-05 19:37:40 +03:00
Koitharu
55d824bb94 Remove header from drawer 2020-08-31 19:40:58 +03:00
Koitharu
229d9fa2ae Update launcher icon 2020-08-31 19:37:14 +03:00
Koitharu
3eb68e1ff9 Manual screen rotation in reader 2020-08-28 21:01:59 +03:00
Koitharu
b103589bba Fix unsupported image formats in reader 2020-08-27 19:54:53 +03:00
Koitharu
0726c037a4 Update dependencies 2020-08-26 16:14:32 +03:00
Koitharu
0ff64931e0 Close drawer on back pressed 2020-08-09 16:54:00 +03:00
87 changed files with 591 additions and 382 deletions

View File

@@ -5,9 +5,9 @@ jdk:
android: android:
components: components:
- tools - tools
- platform-tools-29.0.6 - platform-tools-30.0.3
- build-tools-29.0.3 - build-tools-30.0.2
- android-29 - android-30
licenses: licenses:
- android-sdk-preview-license-.+ - android-sdk-preview-license-.+
- android-sdk-license-.+ - android-sdk-license-.+

View File

@@ -8,15 +8,15 @@ plugins {
def gitCommits = 'git rev-list --count HEAD'.execute([], rootDir).text.trim().toInteger() def gitCommits = 'git rev-list --count HEAD'.execute([], rootDir).text.trim().toInteger()
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion '29.0.3' buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 30
versionCode gitCommits versionCode gitCommits
versionName '0.5' versionName '0.5.2'
kapt { kapt {
arguments { arguments {
@@ -28,10 +28,6 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
}
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix = '.debug' applicationIdSuffix = '.debug'
@@ -55,25 +51,31 @@ android {
androidExtensions { androidExtensions {
experimental = true experimental = true
} }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += ['-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi', '-Xjvm-default=all']
}
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation 'androidx.core:core-ktx:1.5.0-alpha01' implementation 'androidx.core:core-ktx:1.5.0-alpha03'
implementation 'androidx.activity:activity-ktx:1.2.0-alpha06' implementation 'androidx.activity:activity-ktx:1.2.0-alpha08'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha06' implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha08'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8' implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha04' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.4.0-rc01' implementation 'androidx.work:work-runtime-ktx:2.4.0'
implementation 'com.google.android.material:material:1.3.0-alpha01' implementation 'com.google.android.material:material:1.3.0-alpha02'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-alpha05' kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-alpha07'
implementation 'androidx.room:room-runtime:2.2.5' implementation 'androidx.room:room-runtime:2.2.5'
implementation 'androidx.room:room-ktx:2.2.5' implementation 'androidx.room:room-ktx:2.2.5'
@@ -85,12 +87,12 @@ dependencies {
implementation 'com.github.moxy-community:moxy-ktx:2.1.2' implementation 'com.github.moxy-community:moxy-ktx:2.1.2'
kapt 'com.github.moxy-community:moxy-compiler:2.1.2' kapt 'com.github.moxy-community:moxy-compiler:2.1.2'
implementation 'com.squareup.okhttp3:okhttp:4.8.0' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okio:okio:2.7.0' implementation 'com.squareup.okio:okio:2.8.0'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.koin:koin-android:2.1.5' implementation 'org.koin:koin-android:2.2.0-beta-1'
implementation 'io.coil-kt:coil:0.11.0' implementation 'io.coil-kt:coil:1.0.0-rc2'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0' implementation 'com.tomclaw.cache:cache:1.0'

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#E6000A">
<group android:scaleX="0.40188664"
android:scaleY="0.40188664"
android:translateX="32.90095"
android:translateY="18.7272">
<group android:translateY="139.39206">
<path android:pathData="M83.796875,-0L105.6875,-0L60.765625,-55.828125L103.09375,-101L82.078125,-101L32.25,-49.1875L32.25,-101L13.53125,-101L13.53125,-0L32.25,-0L32.25,-25.8125L48.234375,-42.265625L83.796875,-0Z"
android:fillColor="#E6000A"/>
</group>
</group>
</vector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -75,7 +75,6 @@
<service <service
android:name=".ui.download.DownloadService" android:name=".ui.download.DownloadService"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service android:name=".ui.settings.AppUpdateService" />
<service <service
android:name=".ui.widget.shelf.ShelfWidgetService" android:name=".ui.widget.shelf.ShelfWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />

View File

@@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.room.Room import androidx.room.Room
import coil.Coil import coil.Coil
import coil.ComponentRegistry import coil.ComponentRegistry
import coil.ImageLoaderBuilder import coil.ImageLoader
import coil.util.CoilUtils import coil.util.CoilUtils
import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
@@ -14,6 +14,7 @@ import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.db.DatabasePrePopulateCallback import org.koitharu.kotatsu.core.db.DatabasePrePopulateCallback
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -73,7 +74,7 @@ class KotatsuApp : Application() {
private fun initKoin() { private fun initKoin() {
startKoin { startKoin {
androidLogger() androidLogger(Level.ERROR)
androidContext(applicationContext) androidContext(applicationContext)
modules( modules(
module { module {
@@ -101,7 +102,7 @@ class KotatsuApp : Application() {
private fun initCoil() { private fun initCoil() {
Coil.setImageLoader( Coil.setImageLoader(
ImageLoaderBuilder(applicationContext) ImageLoader.Builder(applicationContext)
.okHttpClient( .okHttpClient(
okHttp() okHttp()
.cache(CoilUtils.createDefaultCache(applicationContext)) .cache(CoilUtils.createDefaultCache(applicationContext))

View File

@@ -4,19 +4,19 @@ import androidx.room.*
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
@Dao @Dao
interface TagsDao { abstract class TagsDao {
@Query("SELECT * FROM tags") @Query("SELECT * FROM tags")
suspend fun getAllTags(): List<TagEntity> abstract suspend fun getAllTags(): List<TagEntity>
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(tag: TagEntity): Long abstract suspend fun insert(tag: TagEntity): Long
@Update(onConflict = OnConflictStrategy.IGNORE) @Update(onConflict = OnConflictStrategy.IGNORE)
suspend fun update(tag: TagEntity): Int abstract suspend fun update(tag: TagEntity): Int
@Transaction @Transaction
suspend fun upsert(tags: Iterable<TagEntity>) { open suspend fun upsert(tags: Iterable<TagEntity>) {
tags.forEach { tag -> tags.forEach { tag ->
if (update(tag) <= 0) { if (update(tag) <= 0) {
insert(tag) insert(tag)

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.local
import android.net.Uri import android.net.Uri
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import coil.bitmappool.BitmapPool import coil.bitmap.BitmapPool
import coil.decode.DataSource import coil.decode.DataSource
import coil.decode.Options import coil.decode.Options
import coil.fetch.FetchResult import coil.fetch.FetchResult

View File

@@ -131,7 +131,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
override fun onCreatePreferences() = setOf(R.string.key_parser_domain) override fun onCreatePreferences() = setOf(R.string.key_parser_domain)
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) { when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) {
SortOrder.ALPHABETICAL -> "catalog" SortOrder.ALPHABETICAL -> "catalog"
SortOrder.POPULARITY -> "mostfavorites" SortOrder.POPULARITY -> "mostfavorites"
SortOrder.NEWEST -> "manga/new" SortOrder.NEWEST -> "manga/new"
@@ -139,7 +139,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
} }
private fun getSortKey2(sortOrder: SortOrder?) = private fun getSortKey2(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) { when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) {
SortOrder.ALPHABETICAL -> "abcasc" SortOrder.ALPHABETICAL -> "abcasc"
SortOrder.POPULARITY -> "favdesc" SortOrder.POPULARITY -> "favdesc"
SortOrder.NEWEST -> "datedesc" SortOrder.NEWEST -> "datedesc"

View File

@@ -165,7 +165,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
override fun onCreatePreferences() = setOf(R.string.key_parser_domain) override fun onCreatePreferences() = setOf(R.string.key_parser_domain)
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) { when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) {
SortOrder.ALPHABETICAL -> "name" SortOrder.ALPHABETICAL -> "name"
SortOrder.POPULARITY -> "rate" SortOrder.POPULARITY -> "rate"
SortOrder.UPDATED -> "updated" SortOrder.UPDATED -> "updated"

View File

@@ -1,10 +1,7 @@
package org.koitharu.kotatsu.core.parser.site package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.parseHtml import org.koitharu.kotatsu.utils.ext.parseHtml
@@ -12,9 +9,25 @@ import org.koitharu.kotatsu.utils.ext.withDomain
class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) { class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {
override val defaultDomain = "henchan.pro" override val defaultDomain = "hentaichan.pro"
override val source = MangaSource.HENCHAN override val source = MangaSource.HENCHAN
override suspend fun getList(
offset: Int,
query: String?,
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
return super.getList(offset, query, sortOrder, tag).map {
val cover = it.coverUrl
if (cover.contains("_blur")) {
it.copy(coverUrl = cover.replace("_blur", ""))
} else {
it
}
}
}
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(defaultDomain) val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml() val doc = loaderContext.httpGet(manga.url).parseHtml()

View File

@@ -27,7 +27,7 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClient(), Ko
override fun onPageCommitVisible(view: WebView, url: String?) { override fun onPageCommitVisible(view: WebView, url: String?) {
super.onPageCommitVisible(view, url) super.onPageCommitVisible(view, url)
callback.onTitleChanged(view.title, url) callback.onTitleChanged(view.title.orEmpty(), url)
} }
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?) = false override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?) = false

View File

@@ -6,7 +6,7 @@ import androidx.core.net.toUri
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import coil.api.load import coil.load
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_details.* import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@@ -13,7 +13,6 @@ import androidx.core.graphics.drawable.toBitmap
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.utils.ext.clearActions
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import kotlin.math.roundToInt import kotlin.math.roundToInt

View File

@@ -8,7 +8,7 @@ import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@@ -89,7 +89,7 @@ class DownloadService : BaseService() {
val repo = MangaProviderFactory.create(manga.source) val repo = MangaProviderFactory.create(manga.source)
val cover = safe { val cover = safe {
Coil.execute( Coil.execute(
GetRequestBuilder(this@DownloadService) ImageRequest.Builder(this@DownloadService)
.data(manga.coverUrl) .data(manga.coverUrl)
.build() .build()
).drawable ).drawable

View File

@@ -92,6 +92,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
drawerToggle.onConfigurationChanged(newConfig) drawerToggle.onConfigurationChanged(newConfig)
} }
override fun onBackPressed() {
if (drawer.isDrawerOpen(navigationView)) {
drawer.closeDrawer(navigationView)
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.opt_main, menu) menuInflater.inflate(R.menu.opt_main, menu)
menu.findItem(R.id.action_search)?.let { menuItem -> menu.findItem(R.id.action_search)?.let { menuItem ->

View File

@@ -1,8 +1,8 @@
package org.koitharu.kotatsu.ui.list package org.koitharu.kotatsu.ui.list
import android.view.ViewGroup import android.view.ViewGroup
import coil.api.clear import coil.clear
import coil.api.load import coil.load
import kotlinx.android.synthetic.main.item_manga_grid.* import kotlinx.android.synthetic.main.item_manga_grid.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga

View File

@@ -3,8 +3,8 @@ package org.koitharu.kotatsu.ui.list
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.api.clear import coil.clear
import coil.api.load import coil.load
import kotlinx.android.synthetic.main.item_manga_list_details.* import kotlinx.android.synthetic.main.item_manga_list_details.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga

View File

@@ -1,8 +1,8 @@
package org.koitharu.kotatsu.ui.list package org.koitharu.kotatsu.ui.list
import android.view.ViewGroup import android.view.ViewGroup
import coil.api.clear import coil.clear
import coil.api.load import coil.load
import kotlinx.android.synthetic.main.item_manga_list.* import kotlinx.android.synthetic.main.item_manga_list.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.ui.list.feed
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -16,6 +17,7 @@ import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener
import org.koitharu.kotatsu.ui.common.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.ui.common.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.ui.tracker.TrackWorker
import org.koitharu.kotatsu.utils.ext.callOnScrollListeners import org.koitharu.kotatsu.utils.ext.callOnScrollListeners
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hasItems import org.koitharu.kotatsu.utils.ext.hasItems
@@ -53,6 +55,15 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), FeedView,
inflater.inflate(R.menu.opt_feed, menu) inflater.inflate(R.menu.opt_feed, menu)
} }
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
R.id.action_update -> {
TrackWorker.startNow(requireContext())
Snackbar.make(recyclerView, R.string.feed_will_update_soon, Snackbar.LENGTH_LONG).show()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onDestroyView() { override fun onDestroyView() {
adapter = null adapter = null
super.onDestroyView() super.onDestroyView()

View File

@@ -2,8 +2,8 @@ package org.koitharu.kotatsu.ui.list.feed
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.ViewGroup import android.view.ViewGroup
import coil.api.clear import coil.clear
import coil.api.load import coil.load
import kotlinx.android.synthetic.main.item_tracklog.* import kotlinx.android.synthetic.main.item_tracklog.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.TrackingLogItem import org.koitharu.kotatsu.core.model.TrackingLogItem

View File

@@ -21,6 +21,10 @@ import java.io.File
class LocalListFragment : MangaListFragment<File>(), ActivityResultCallback<Uri> { class LocalListFragment : MangaListFragment<File>(), ActivityResultCallback<Uri> {
private val presenter by moxyPresenter(factory = ::LocalListPresenter) private val presenter by moxyPresenter(factory = ::LocalListPresenter)
private val importCall = registerForActivityResult(
ActivityResultContracts.OpenDocument(),
this
)
override fun onRequestMoreItems(offset: Int) { override fun onRequestMoreItems(offset: Int) {
presenter.loadList(offset) presenter.loadList(offset)
@@ -35,8 +39,7 @@ class LocalListFragment : MangaListFragment<File>(), ActivityResultCallback<Uri>
return when (item.itemId) { return when (item.itemId) {
R.id.action_import -> { R.id.action_import -> {
try { try {
registerForActivityResult(ActivityResultContracts.OpenDocument(), this) importCall.launch(arrayOf("*/*"))
.launch(arrayOf("*/*"))
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()

View File

@@ -1,8 +1,12 @@
package org.koitharu.kotatsu.ui.reader package org.koitharu.kotatsu.ui.reader
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.util.ArrayMap import android.util.ArrayMap
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
@@ -20,6 +24,7 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle {
private val tasks = ArrayMap<String, Deferred<File>>() private val tasks = ArrayMap<String, Deferred<File>>()
private val okHttp by inject<OkHttpClient>() private val okHttp by inject<OkHttpClient>()
private val cache by inject<PagesCache>() private val cache by inject<PagesCache>()
private val convertLock = Mutex()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job get() = Dispatchers.Main + job
@@ -67,6 +72,21 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle {
} }
} }
suspend fun convertInPlace(file: File) {
convertLock.withLock(file) {
withContext(Dispatchers.IO) {
val image = BitmapFactory.decodeFile(file.absolutePath)
try {
file.outputStream().use { out ->
image.compress(Bitmap.CompressFormat.WEBP, 100, out)
}
} finally {
image.recycle()
}
}
}
}
override fun dispose() { override fun dispose() {
coroutineContext.cancel() coroutineContext.cancel()
tasks.clear() tasks.clear()

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.ui.reader package org.koitharu.kotatsu.ui.reader
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
@@ -17,10 +18,13 @@ import androidx.core.view.isVisible
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moxy.MvpDelegate import moxy.MvpDelegate
import moxy.ktx.moxyPresenter import moxy.ktx.moxyPresenter
@@ -40,6 +44,7 @@ import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment
import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.anim.Motion import org.koitharu.kotatsu.utils.anim.Motion
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
@@ -56,6 +61,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
private set private set
private lateinit var touchHelper: GridTouchHelper private lateinit var touchHelper: GridTouchHelper
private lateinit var orientationHelper: ScreenOrientationHelper
private var isTapSwitchEnabled = true private var isTapSwitchEnabled = true
private var isVolumeKeysSwitchEnabled = false private var isVolumeKeysSwitchEnabled = false
@@ -67,6 +73,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
setContentView(R.layout.activity_reader) setContentView(R.layout.activity_reader)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
touchHelper = GridTouchHelper(this, this) touchHelper = GridTouchHelper(this, this)
orientationHelper = ScreenOrientationHelper(this)
toolbar_bottom.inflateMenu(R.menu.opt_reader_bottom) toolbar_bottom.inflateMenu(R.menu.opt_reader_bottom)
toolbar_bottom.setOnMenuItemClickListener(::onOptionsItemSelected) toolbar_bottom.setOnMenuItemClickListener(::onOptionsItemSelected)
@@ -89,6 +96,10 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
settings.subscribe(this) settings.subscribe(this)
loadSettings() loadSettings()
orientationHelper.observeAutoOrientation()
.onEach {
toolbar_bottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it
}.launchIn(lifecycleScope)
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.init(state.manga) presenter.init(state.manga)
@@ -165,6 +176,10 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
) )
true true
} }
R.id.action_screen_rotate -> {
orientationHelper.toggleOrientation()
true
}
R.id.action_pages_thumbs -> { R.id.action_pages_thumbs -> {
if (reader?.hasItems == true) { if (reader?.hasItems == true) {
val pages = reader?.getPages() val pages = reader?.getPages()
@@ -325,14 +340,18 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
presenter.setMode(state.manga, mode) presenter.setMode(state.manga, mode)
} }
@SuppressLint("ShowToast")
override fun onPageSaved(uri: Uri?) { override fun onPageSaved(uri: Uri?) {
if (uri != null) { if (uri != null) {
Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG) Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG)
.setAnchorView(appbar_bottom)
.setAction(R.string.share) { .setAction(R.string.share) {
ShareHelper.shareImage(this, uri) ShareHelper.shareImage(this, uri)
}.show() }.show()
} else { } else {
Snackbar.make(container, R.string.error_occurred, Snackbar.LENGTH_SHORT).show() Snackbar.make(container, R.string.error_occurred, Snackbar.LENGTH_SHORT)
.setAnchorView(appbar_bottom)
.show()
} }
} }
@@ -369,8 +388,16 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
} }
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
appbar_top.updatePadding(top = insets.systemWindowInsetTop) appbar_top.updatePadding(
appbar_bottom.updatePadding(bottom = insets.systemWindowInsetBottom) top = insets.systemWindowInsetTop,
right = insets.systemWindowInsetRight,
left = insets.systemWindowInsetLeft
)
appbar_bottom.updatePadding(
bottom = insets.systemWindowInsetBottom,
right = insets.systemWindowInsetRight,
left = insets.systemWindowInsetLeft
)
return insets.consumeSystemWindowInsets() return insets.consumeSystemWindowInsets()
} }

View File

@@ -0,0 +1,106 @@
package org.koitharu.kotatsu.ui.reader.base
import android.net.Uri
import androidx.core.net.toUri
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.coroutines.*
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.utils.ext.launchAfter
import org.koitharu.kotatsu.utils.ext.launchInstead
import java.io.File
import java.io.IOException
class PageHolderDelegate(
private val loader: PageLoader,
private val callback: Callback
) : SubsamplingScaleImageView.DefaultOnImageEventListener(), CoroutineScope by loader {
private var state = State.EMPTY
private var job: Job? = null
private var file: File? = null
fun onBind(page: MangaPage) {
doLoad(page, force = false)
}
fun retry(page: MangaPage) {
doLoad(page, force = true)
}
fun onRecycle() {
state = State.EMPTY
file = null
job?.cancel()
}
override fun onReady() {
state = State.SHOWING
callback.onImageShowing()
}
override fun onImageLoaded() {
state = State.SHOWN
callback.onImageShown()
}
override fun onImageLoadError(e: Exception) {
val file = this.file
if (state == State.LOADED && e is IOException && file != null && file.exists()) {
job = launchAfter(job) {
state = State.CONVERTING
try {
loader.convertInPlace(file)
state = State.CONVERTED
callback.onImageReady(file.toUri())
} catch (e2: Throwable) {
e2.addSuppressed(e)
state = State.ERROR
callback.onError(e2)
}
}
} else {
state = State.ERROR
callback.onError(e)
}
}
private fun doLoad(data: MangaPage, force: Boolean) {
job = launchInstead(job) {
state = State.LOADING
callback.onLoadingStarted()
try {
val file = withContext(Dispatchers.IO) {
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
loader.loadFile(pageUrl, force)
}
this@PageHolderDelegate.file = file
state = State.LOADED
callback.onImageReady(file.toUri())
} catch (e: CancellationException) {
//do nothing
} catch (e: Exception) {
state = State.ERROR
callback.onError(e)
}
}
}
private enum class State {
EMPTY, LOADING, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
}
interface Callback {
fun onLoadingStarted()
fun onError(e: Throwable)
fun onImageReady(uri: Uri)
fun onImageShowing()
fun onImageShown()
}
}

View File

@@ -1,79 +1,67 @@
package org.koitharu.kotatsu.ui.reader.standard package org.koitharu.kotatsu.ui.reader.standard
import android.net.Uri
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page.* import kotlinx.android.synthetic.main.item_page.*
import kotlinx.coroutines.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class PageHolder(parent: ViewGroup, private val loader: PageLoader) : class PageHolder(parent: ViewGroup, loader: PageLoader) :
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page), BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader { PageHolderDelegate.Callback, View.OnClickListener {
private var job: Job? = null private val delegate = PageHolderDelegate(loader, this)
init { init {
ssiv.setOnImageEventListener(this) ssiv.setOnImageEventListener(delegate)
button_retry.setOnClickListener { button_retry.setOnClickListener(this)
doLoad(boundData ?: return@setOnClickListener, force = true)
}
} }
override fun onBind(data: MangaPage, extra: Unit) { override fun onBind(data: MangaPage, extra: Unit) {
doLoad(data, force = false) delegate.onBind(data)
} }
override fun onRecycled() { override fun onRecycled() {
job?.cancel() delegate.onRecycle()
ssiv.recycle() ssiv.recycle()
} }
private fun doLoad(data: MangaPage, force: Boolean) { override fun onLoadingStarted() {
job?.cancel() layout_error.isVisible = false
job = launch { progressBar.isVisible = true
layout_error.isVisible = false ssiv.recycle()
progressBar.isVisible = true
ssiv.recycle()
try {
val uri = withContext(Dispatchers.IO) {
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
loader.loadFile(pageUrl, force)
}.toUri()
ssiv.setImage(ImageSource.uri(uri))
} catch (e: CancellationException) {
//do nothing
} catch (e: Exception) {
onError(e)
}
}
} }
override fun onReady() { override fun onImageReady(uri: Uri) {
ssiv.maxScale = 2f * maxOf(ssiv.width / ssiv.sWidth.toFloat(), ssiv.height / ssiv.sHeight.toFloat()) ssiv.setImage(ImageSource.uri(uri))
}
override fun onImageShowing() {
ssiv.maxScale = 2f * maxOf(
ssiv.width / ssiv.sWidth.toFloat(),
ssiv.height / ssiv.sHeight.toFloat()
)
ssiv.resetScaleAndCenter() ssiv.resetScaleAndCenter()
} }
override fun onImageLoadError(e: Exception) = onError(e) override fun onImageShown() {
override fun onImageLoaded() {
progressBar.isVisible = false progressBar.isVisible = false
} }
override fun onTileLoadError(e: Exception?) = Unit override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> delegate.retry(boundData ?: return)
}
}
override fun onPreviewReleased() = Unit override fun onError(e: Throwable) {
override fun onPreviewLoadError(e: Exception?) = Unit
private fun onError(e: Throwable) {
textView_error.text = e.getDisplayMessage(context.resources) textView_error.text = e.getDisplayMessage(context.resources)
layout_error.isVisible = true layout_error.isVisible = true
progressBar.isVisible = false progressBar.isVisible = false

View File

@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.ui.reader.thumbnails
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.net.toUri import androidx.core.net.toUri
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import coil.size.PixelSize import coil.size.PixelSize
import coil.size.Size import coil.size.Size
import kotlinx.android.synthetic.main.item_page_thumb.* import kotlinx.android.synthetic.main.item_page_thumb.*
@@ -18,7 +18,7 @@ class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope)
BaseViewHolder<MangaPage, PagesCache>(parent, R.layout.item_page_thumb) { BaseViewHolder<MangaPage, PagesCache>(parent, R.layout.item_page_thumb) {
private var job: Job? = null private var job: Job? = null
private val thumbSize: Size private val thumbSize: Size
init { init {
val width = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) val width = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
@@ -38,10 +38,12 @@ class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope)
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data) val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
extra[pageUrl]?.toUri()?.toString() ?: pageUrl extra[pageUrl]?.toUri()?.toString() ?: pageUrl
} }
val drawable = Coil.execute(GetRequestBuilder(context) val drawable = Coil.execute(
.data(url) ImageRequest.Builder(context)
.size(thumbSize) .data(url)
.build()).drawable .size(thumbSize)
.build()
).drawable
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
imageView_thumb.setImageDrawable(drawable) imageView_thumb.setImageDrawable(drawable)
} }

View File

@@ -1,64 +1,80 @@
package org.koitharu.kotatsu.ui.reader.wetoon package org.koitharu.kotatsu.ui.reader.wetoon
import android.net.Uri
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page_webtoon.* import kotlinx.android.synthetic.main.item_page_webtoon.*
import kotlinx.coroutines.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) : class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon), BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon),
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader { PageHolderDelegate.Callback, View.OnClickListener {
private var job: Job? = null private val delegate = PageHolderDelegate(loader, this)
private var scrollToRestore = 0 private var scrollToRestore = 0
init { init {
ssiv.setOnImageEventListener(this) ssiv.setOnImageEventListener(delegate)
button_retry.setOnClickListener { button_retry.setOnClickListener(this)
doLoad(boundData ?: return@setOnClickListener, force = true)
}
} }
override fun onBind(data: MangaPage, extra: Unit) { override fun onBind(data: MangaPage, extra: Unit) {
doLoad(data, force = false) delegate.onBind(data)
}
private fun doLoad(data: MangaPage, force: Boolean) {
job?.cancel()
scrollToRestore = 0
job = launch {
layout_error.isVisible = false
progressBar.isVisible = true
ssiv.recycle()
try {
val uri = withContext(Dispatchers.IO) {
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
loader.loadFile(pageUrl, force)
}.toUri()
ssiv.setImage(ImageSource.uri(uri))
} catch (e: CancellationException) {
//do nothing
} catch (e: Exception) {
onError(e)
}
}
} }
override fun onRecycled() { override fun onRecycled() {
job?.cancel() delegate.onRecycle()
ssiv.recycle() ssiv.recycle()
} }
override fun onLoadingStarted() {
layout_error.isVisible = false
progressBar.isVisible = true
ssiv.recycle()
}
override fun onImageReady(uri: Uri) {
ssiv.setImage(ImageSource.uri(uri))
}
override fun onImageShowing() {
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
ssiv.scrollTo(
when {
scrollToRestore != 0 -> scrollToRestore
itemView.top < 0 -> ssiv.getScrollRange()
else -> 0
}
)
}
override fun onImageShown() {
progressBar.isVisible = false
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> delegate.retry(boundData ?: return)
}
}
override fun onError(e: Throwable) {
textView_error.text = e.getDisplayMessage(context.resources)
layout_error.isVisible = true
progressBar.isVisible = false
}
fun getScrollY() = ssiv.getScroll() fun getScrollY() = ssiv.getScroll()
fun restoreScroll(scroll: Int) { fun restoreScroll(scroll: Int) {
@@ -68,33 +84,4 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
scrollToRestore = scroll scrollToRestore = scroll
} }
} }
override fun onReady() {
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
ssiv.scrollTo(when {
scrollToRestore != 0 -> scrollToRestore
itemView.top < 0 -> ssiv.getScrollRange()
else -> 0
})
}
override fun onImageLoadError(e: Exception) = onError(e)
override fun onImageLoaded() {
progressBar.isVisible = false
}
override fun onTileLoadError(e: Exception?) = Unit
override fun onPreviewReleased() = Unit
override fun onPreviewLoadError(e: Exception?) = Unit
private fun onError(e: Throwable) {
textView_error.text = e.getDisplayMessage(context.resources)
layout_error.isVisible = true
progressBar.isVisible = false
}
} }

View File

@@ -67,10 +67,15 @@ class AppUpdateChecker(private val activity: ComponentActivity) : KoinComponent
.setTitle(R.string.app_update_available) .setTitle(R.string.app_update_available)
.setMessage(buildString { .setMessage(buildString {
append(activity.getString(R.string.new_version_s, version.name)) append(activity.getString(R.string.new_version_s, version.name))
appendln() appendLine()
append(activity.getString(R.string.size_s, FileSizeUtils.formatBytes(activity, version.apkSize))) append(
appendln() activity.getString(
appendln() R.string.size_s,
FileSizeUtils.formatBytes(activity, version.apkSize)
)
)
appendLine()
appendLine()
append(version.description) append(version.description)
}) })
.setPositiveButton(R.string.download) { _, _ -> .setPositiveButton(R.string.download) { _, _ ->

View File

@@ -3,8 +3,6 @@ package org.koitharu.kotatsu.ui.settings
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.transition.Slide
import android.view.Gravity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.preference.Preference import androidx.preference.Preference
@@ -23,9 +21,7 @@ class SettingsActivity : BaseActivity(),
if (supportFragmentManager.findFragmentById(R.id.container) == null) { if (supportFragmentManager.findFragmentById(R.id.container) == null) {
supportFragmentManager.commit { supportFragmentManager.commit {
replace(R.id.container, MainSettingsFragment().also { replace(R.id.container, MainSettingsFragment())
it.exitTransition = Slide(Gravity.START)
})
} }
} }
} }
@@ -49,10 +45,9 @@ class SettingsActivity : BaseActivity(),
} }
private fun openFragment(fragment: Fragment) { private fun openFragment(fragment: Fragment) {
fragment.enterTransition = Slide(Gravity.END)
fragment.exitTransition = Slide(Gravity.START)
supportFragmentManager.commit { supportFragmentManager.commit {
replace(R.id.container, fragment) replace(R.id.container, fragment)
setReorderingAllowed(true)
addToBackStack(null) addToBackStack(null)
} }
} }

View File

@@ -10,7 +10,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.work.* import androidx.work.*
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
@@ -143,7 +143,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
setNumber(newChapters.size) setNumber(newChapters.size)
setLargeIcon( setLargeIcon(
Coil.execute( Coil.execute(
GetRequestBuilder(applicationContext) ImageRequest.Builder(applicationContext)
.data(manga.coverUrl) .data(manga.coverUrl)
.build() .build()
).toBitmapOrNull() ).toBitmapOrNull()
@@ -222,5 +222,17 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request) .enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)
} }
fun startNow(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<TrackWorker>()
.setConstraints(constraints)
.addTag(TAG)
.build()
WorkManager.getInstance(context)
.enqueue(request)
}
} }
} }

View File

@@ -5,7 +5,7 @@ import android.content.Intent
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@@ -38,9 +38,11 @@ class RecentListFactory(private val context: Context) : RemoteViewsService.Remot
val item = dataSet[position] val item = dataSet[position]
try { try {
val cover = runBlocking { val cover = runBlocking {
Coil.execute(GetRequestBuilder(context) Coil.execute(
.data(item.coverUrl) ImageRequest.Builder(context)
.build()).requireBitmap() .data(item.coverUrl)
.build()
).requireBitmap()
} }
views.setImageViewBitmap(R.id.imageView_cover, cover) views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) { } catch (e: IOException) {

View File

@@ -5,7 +5,7 @@ import android.content.Intent
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@@ -49,9 +49,11 @@ class ShelfListFactory(private val context: Context, widgetId: Int) : RemoteView
views.setTextViewText(R.id.textView_title, item.title) views.setTextViewText(R.id.textView_title, item.title)
try { try {
val cover = runBlocking { val cover = runBlocking {
Coil.execute(GetRequestBuilder(context) Coil.execute(
.data(item.coverUrl) ImageRequest.Builder(context)
.build()).requireBitmap() .data(item.coverUrl)
.build()
).requireBitmap()
} }
views.setImageViewBitmap(R.id.imageView_cover, cover) views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) { } catch (e: IOException) {

View File

@@ -10,7 +10,7 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import coil.Coil import coil.Coil
import coil.request.GetRequestBuilder import coil.request.ImageRequest
import coil.size.PixelSize import coil.size.PixelSize
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -40,7 +40,7 @@ class MangaShortcut(private val manga: Manga) {
} }
builder.setRank(1) builder.setRank(1)
if (shortcuts.isNotEmpty() && shortcuts.size >= limit) { if (shortcuts.isNotEmpty() && shortcuts.size >= limit) {
manager.removeDynamicShortcuts(listOf(shortcuts.minBy { it.rank }!!.id)) manager.removeDynamicShortcuts(listOf(shortcuts.minByOrNull { it.rank }!!.id))
} }
manager.addDynamicShortcuts(listOf(builder.build().toShortcutInfo())) manager.addDynamicShortcuts(listOf(builder.build().toShortcutInfo()))
} }
@@ -66,9 +66,11 @@ class MangaShortcut(private val manga: Manga) {
val icon = safe { val icon = safe {
val size = getIconSize(context) val size = getIconSize(context)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val bmp = Coil.execute(GetRequestBuilder(context) val bmp = Coil.execute(
.data(manga.coverUrl) ImageRequest.Builder(context)
.build()).requireBitmap() .data(manga.coverUrl)
.build()
).requireBitmap()
ThumbnailUtils.extractThumbnail(bmp, size.width, size.height, 0) ThumbnailUtils.extractThumbnail(bmp, size.width, size.height, 0)
} }
} }
@@ -78,7 +80,7 @@ class MangaShortcut(private val manga: Manga) {
.setLongLabel(manga.title) .setLongLabel(manga.title)
.setIcon(icon?.let { .setIcon(icon?.let {
IconCompat.createWithAdaptiveBitmap(it) IconCompat.createWithAdaptiveBitmap(it)
} ?: IconCompat.createWithResource(context, R.drawable.ic_launcher_foreground)) } ?: IconCompat.createWithResource(context, R.drawable.ic_shortcut_default))
.setIntent( .setIntent(
MangaDetailsActivity.newIntent(context, manga.id) MangaDetailsActivity.newIntent(context, manga.id)
.setAction(MangaDetailsActivity.ACTION_MANGA_VIEW) .setAction(MangaDetailsActivity.ACTION_MANGA_VIEW)

View File

@@ -0,0 +1,53 @@
package org.koitharu.kotatsu.utils
import android.app.Activity
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
class ScreenOrientationHelper(private val activity: Activity) {
val isAutoRotationEnabled: Boolean
get() = Settings.System.getInt(
activity.contentResolver,
Settings.System.ACCELEROMETER_ROTATION,
0
) == 1
var isLandscape: Boolean
get() = activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
set(value) {
activity.requestedOrientation = if (value) {
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
}
fun toggleOrientation() {
isLandscape = !isLandscape
}
fun observeAutoOrientation() = callbackFlow<Boolean> {
val observer = object : ContentObserver(Handler(activity.mainLooper)) {
override fun onChange(selfChange: Boolean) {
sendBlocking(isAutoRotationEnabled)
}
}
activity.contentResolver.registerContentObserver(
Settings.System.CONTENT_URI, true, observer
)
awaitClose {
activity.contentResolver.unregisterContentObserver(observer)
}
}.onStart {
emit(isAutoRotationEnabled)
}.distinctUntilChanged()
}

View File

@@ -2,15 +2,15 @@ package org.koitharu.kotatsu.utils.ext
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import coil.request.ErrorResult import coil.request.ErrorResult
import coil.request.RequestResult import coil.request.ImageResult
import coil.request.SuccessResult import coil.request.SuccessResult
fun RequestResult.requireBitmap() = when(this) { fun ImageResult.requireBitmap() = when (this) {
is SuccessResult -> drawable.toBitmap() is SuccessResult -> drawable.toBitmap()
is ErrorResult -> throw throwable is ErrorResult -> throw throwable
} }
fun RequestResult.toBitmapOrNull() = when(this) { fun ImageResult.toBitmapOrNull() = when (this) {
is SuccessResult -> try { is SuccessResult -> try {
drawable.toBitmap() drawable.toBitmap()
} catch (_: Throwable) { } catch (_: Throwable) {

View File

@@ -1,12 +1,15 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
import okhttp3.Response import okhttp3.Response
import org.koitharu.kotatsu.BuildConfig
import java.io.IOException import java.io.IOException
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
@@ -37,4 +40,36 @@ fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
isFirstCall = false isFirstCall = false
} }
} }
}
fun CoroutineScope.launchAfter(
job: Job?,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launch(context, start) {
try {
job?.join()
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
}
block()
}
fun CoroutineScope.launchInstead(
job: Job?,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launch(context, start) {
try {
job?.cancelAndJoin()
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
}
block()
} }

View File

@@ -1,12 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import android.annotation.SuppressLint
import androidx.core.app.NotificationCompat
@SuppressLint("RestrictedApi")
fun NotificationCompat.Builder.clearActions(): NotificationCompat.Builder {
safe {
mActions.clear()
}
return this
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:tint="#0D47A1"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.40188664"
android:scaleY="0.40188664"
android:translateX="32.90095"
android:translateY="18.7272">
<group android:translateY="139.39206">
<path
android:fillColor="#0D47A1"
android:pathData="M83.796875,-0L105.6875,-0L60.765625,-55.828125L103.09375,-101L82.078125,-101L32.25,-49.1875L32.25,-101L13.53125,-101L13.53125,-0L32.25,-0L32.25,-25.8125L48.234375,-42.265625L83.796875,-0Z" />
</group>
</group>
</vector>

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:textColorPrimary"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z" />
</vector>

View File

@@ -1,62 +1,61 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout <androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.list.MainActivity" tools:context=".ui.list.MainActivity"
tools:openDrawer="start"> tools:openDrawer="start">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?colorPrimary" android:background="?colorPrimary"
android:theme="@style/AppToolbarTheme"> android:theme="@style/AppToolbarTheme">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar" android:id="@id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" /> app:layout_scrollFlags="scroll|enterAlways" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@id/container" android:id="@id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:src="@drawable/ic_read_fill" android:src="@drawable/ic_read_fill"
android:visibility="gone" android:visibility="gone"
app:fabSize="normal" app:backgroundTint="?colorAccent"
app:backgroundTint="?colorAccent" app:fabSize="normal"
app:layout_anchor="@id/container" app:layout_anchor="@id/container"
app:layout_anchorGravity="bottom|end" app:layout_anchorGravity="bottom|end"
app:layout_dodgeInsetEdges="bottom" app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" app:layout_dodgeInsetEdges="bottom"
tools:visibility="visible" /> tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView <com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView" android:id="@+id/navigationView"
android:layout_width="260dp" android:layout_width="260dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
app:headerLayout="@layout/view_nav_header" app:menu="@menu/nav_drawer" />
app:menu="@menu/nav_drawer" />
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -5,10 +5,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cardBackgroundColor="?android:windowBackground"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardMaxElevation="0dp" app:cardMaxElevation="0dp"
app:strokeColor="?android:colorControlNormal" app:strokeColor="?colorOnSurface"
app:strokeWidth="1px"> app:strokeWidth="1px">
<LinearLayout <LinearLayout
@@ -30,7 +29,7 @@
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:lines="2" android:lines="2"
android:padding="6dp" android:padding="6dp"
android:text="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />
</LinearLayout> </LinearLayout>

View File

@@ -5,10 +5,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/manga_list_details_item_height" android:layout_height="@dimen/manga_list_details_item_height"
app:cardBackgroundColor="?android:windowBackground"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardMaxElevation="0dp" app:cardMaxElevation="0dp"
app:strokeColor="?android:colorControlNormal" app:strokeColor="?colorOnSurface"
app:strokeWidth="1px"> app:strokeWidth="1px">
<LinearLayout <LinearLayout

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="92dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_foreground"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:listDivider"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@@ -1,2 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu /> <menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_update"
android:orderInCategory="50"
android:title="@string/update"
app:showAsAction="never" />
</menu>

View File

@@ -1,10 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu <menu
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction"> tools:ignore="AlwaysShowAction">
<item
android:id="@+id/action_screen_rotate"
android:icon="@drawable/ic_screen_rotation"
android:title="@string/rotate_screen"
android:visible="false"
app:showAsAction="always" />
<item <item
android:id="@+id/action_bookmark_add" android:id="@+id/action_bookmark_add"
android:icon="@drawable/ic_bookmark_add" android:icon="@drawable/ic_bookmark_add"
@@ -12,12 +19,6 @@
android:visible="false" android:visible="false"
app:showAsAction="always" /> app:showAsAction="always" />
<item
android:id="@+id/action_save_page"
android:icon="@drawable/ic_page_image"
android:title="@string/save_page"
app:showAsAction="always" />
<item <item
android:id="@+id/action_pages_thumbs" android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid" android:icon="@drawable/ic_grid"
@@ -30,10 +31,14 @@
android:title="@string/read_mode" android:title="@string/read_mode"
app:showAsAction="always" /> app:showAsAction="always" />
<item
android:id="@+id/action_save_page"
android:title="@string/save_page"
app:showAsAction="never" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:icon="@drawable/ic_tune"
android:title="@string/settings" android:title="@string/settings"
app:showAsAction="always" /> app:showAsAction="never" />
</menu> </menu>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon
<background android:drawable="@color/ic_launcher_background"/> xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <background android:drawable="@color/blue_primary" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/blue_primary"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -59,7 +59,7 @@
<string name="_s_deleted_from_local_storage">\"%s\" удалено с устройства</string> <string name="_s_deleted_from_local_storage">\"%s\" удалено с устройства</string>
<string name="wait_for_loading_finish">Дождитесь окончания загрузки</string> <string name="wait_for_loading_finish">Дождитесь окончания загрузки</string>
<string name="save_page">Сохранить страницу</string> <string name="save_page">Сохранить страницу</string>
<string name="page_saved">Страницы сохранена</string> <string name="page_saved">Страница сохранена</string>
<string name="share_image">Поделиться изображением</string> <string name="share_image">Поделиться изображением</string>
<string name="_import">Импорт</string> <string name="_import">Импорт</string>
<string name="delete">Удалить</string> <string name="delete">Удалить</string>
@@ -142,4 +142,7 @@
<string name="waiting_for_network">Ожидание подключения…</string> <string name="waiting_for_network">Ожидание подключения…</string>
<string name="clear_updates_feed">Очистить ленту обновлений</string> <string name="clear_updates_feed">Очистить ленту обновлений</string>
<string name="updates_feed_cleared">Лента обновлений очищена</string> <string name="updates_feed_cleared">Лента обновлений очищена</string>
<string name="rotate_screen">Повернуть экран</string>
<string name="update">Обновить</string>
<string name="feed_will_update_soon">Обновление скоро начнётся</string>
</resources> </resources>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="blue_primary">#0288D1</color> <color name="blue_primary">#1976D2</color>
<color name="blue_primary_dark">#0D47A1</color> <color name="blue_primary_dark">#1565C0</color>
<color name="red_accent">#F4511E</color> <color name="red_accent">#EF5350</color>
<color name="grey">#424242</color> <color name="grey">#424242</color>
<color name="grey_dark">#212121</color> <color name="grey_dark">#212121</color>
<color name="dim">#99000000</color> <color name="dim">#99000000</color>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -143,4 +143,7 @@
<string name="waiting_for_network">Waiting for network…</string> <string name="waiting_for_network">Waiting for network…</string>
<string name="clear_updates_feed">Clear updates feed</string> <string name="clear_updates_feed">Clear updates feed</string>
<string name="updates_feed_cleared">Updates feed cleared</string> <string name="updates_feed_cleared">Updates feed cleared</string>
<string name="rotate_screen">Rotate screen</string>
<string name="update">Update</string>
<string name="feed_will_update_soon">Feed update will start soon</string>
</resources> </resources>

View File

@@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.3.72" ext.kotlin_version = '1.4.10'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha04' classpath 'com.android.tools.build:gradle:4.1.0-rc03'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@@ -1,22 +1,18 @@
# Project-wide Gradle settings. ## For more details on how to configure your build environment visit
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m # Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the #Sat Sep 19 17:19:33 EEST 2020
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": android.nonTransitiveRClass=true
android.useAndroidX=true
kotlin.code.style=official kotlin.code.style=official
android.nonTransitiveRClass=true org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M"

View File

@@ -1,6 +1,6 @@
#Wed Jul 01 18:26:34 EEST 2020 #Wed Aug 26 15:09:14 EEST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip