diff --git a/app/build.gradle b/app/build.gradle index 44459e0332..c503566e9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,6 +202,7 @@ dependencies { implementation 'me.zhanghai.android.systemuihelper:library:1.0.0' implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4' implementation 'com.github.mthli:Slice:v1.2' + implementation 'me.gujun.android.taggroup:library:1.4@aar' // Conductor implementation "com.bluelinelabs:conductor:2.1.4" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index f919b84033..4606ce585a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -185,10 +185,11 @@ class CatalogueController : NucleusController(), // Create query listener which opens the global search view. searchView.queryTextChangeEvents() .filter { it.isSubmitted } - .subscribeUntilDestroy { - val query = it.queryText().toString() - router.pushController(CatalogueSearchController(query).withFadeTransaction()) - } + .subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) } + } + + fun performGlobalSearch(query: String){ + router.pushController(CatalogueSearchController(query).withFadeTransaction()) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index ed9636f6ba..1301bff9d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -21,10 +21,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.RouterPagerAdapter -import eu.kanade.tachiyomi.ui.base.controller.RxController -import eu.kanade.tachiyomi.ui.base.controller.TabbedController -import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe +import eu.kanade.tachiyomi.ui.base.controller.* +import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController import eu.kanade.tachiyomi.ui.manga.track.TrackController @@ -34,6 +32,7 @@ import kotlinx.android.synthetic.main.manga_controller.* import rx.Subscription import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.* class MangaController : RxController, TabbedController { @@ -63,6 +62,8 @@ class MangaController : RxController, TabbedController { val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) + val lastUpdateRelay: BehaviorRelay = BehaviorRelay.create() + val chapterCountRelay: BehaviorRelay = BehaviorRelay.create() val mangaFavoriteRelay: PublishRelay = PublishRelay.create() @@ -188,4 +189,5 @@ class MangaController : RxController, TabbedController { .apply { isAccessible = true } } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index b58073d544..827c9a9220 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.support.design.widget.Snackbar @@ -61,7 +62,7 @@ class ChaptersController : NucleusController(), override fun createPresenter(): ChaptersPresenter { val ctrl = parentController as MangaController return ChaptersPresenter(ctrl.manga!!, ctrl.source!!, - ctrl.chapterCountRelay, ctrl.mangaFavoriteRelay) + ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay) } override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { @@ -292,6 +293,7 @@ class ChaptersController : NucleusController(), return true } + @SuppressLint("StringFormatInvalid") override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { val count = adapter?.selectedItemCount ?: 0 if (count == 0) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 923aec8666..3a2efcc5c2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -20,6 +20,7 @@ import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.* /** * Presenter of [ChaptersController]. @@ -28,6 +29,7 @@ class ChaptersPresenter( val manga: Manga, val source: Source, private val chapterCountRelay: BehaviorRelay, + private val lastUpdateRelay: BehaviorRelay, private val mangaFavoriteRelay: PublishRelay, val preferences: PreferencesHelper = Injekt.get(), private val db: DatabaseHelper = Injekt.get(), @@ -91,6 +93,11 @@ class ChaptersPresenter( // Emit the number of chapters to the info tab. chapterCountRelay.call(chapters.maxBy { it.chapter_number }?.chapter_number ?: 0f) + + // Emit the upload date of the most recent chapter + lastUpdateRelay.call(Date(chapters.maxBy { it.date_upload }?.date_upload + ?: 0)) + } .subscribe { chaptersRelay.call(it) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 4defb6ec9f..1963305960 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -2,8 +2,12 @@ package eu.kanade.tachiyomi.ui.manga.info import android.app.Dialog import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build @@ -13,6 +17,7 @@ import android.support.v4.content.pm.ShortcutInfoCompat import android.support.v4.content.pm.ShortcutManagerCompat import android.support.v4.graphics.drawable.IconCompat import android.view.* +import android.widget.Toast import com.afollestad.materialdialogs.MaterialDialog import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.RoundedCorners @@ -20,6 +25,7 @@ import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.view.clicks +import com.jakewharton.rxbinding.view.longClicks import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga @@ -31,17 +37,22 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.util.truncateCenter import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.MaskTransformation import kotlinx.android.synthetic.main.manga_info_controller.* import uy.kohesive.injekt.injectLazy +import java.text.DateFormat import java.text.DecimalFormat +import java.util.* /** * Fragment that shows manga information. @@ -64,7 +75,7 @@ class MangaInfoController : NucleusController(), override fun createPresenter(): MangaInfoPresenter { val ctrl = parentController as MangaController return MangaInfoPresenter(ctrl.manga!!, ctrl.source!!, - ctrl.chapterCountRelay, ctrl.mangaFavoriteRelay) + ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay) } override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { @@ -79,6 +90,41 @@ class MangaInfoController : NucleusController(), // Set SwipeRefresh to refresh manga data. swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() } + + manga_full_title.longClicks().subscribeUntilDestroy{ + copyToClipboard(view.context.getString(R.string.title), manga_full_title.text.toString()) + } + + manga_full_title.clicks().subscribeUntilDestroy { + performGlobalSearch(manga_full_title.text.toString()) + } + + manga_artist.longClicks().subscribeUntilDestroy { + copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString()) + } + + manga_artist.clicks().subscribeUntilDestroy { + performGlobalSearch(manga_artist.text.toString()) + } + + manga_author.longClicks().subscribeUntilDestroy { + copyToClipboard(manga_author.text.toString(), manga_author.text.toString()) + } + + manga_author.clicks().subscribeUntilDestroy { + performGlobalSearch(manga_author.text.toString()) + } + + manga_summary.longClicks().subscribeUntilDestroy { + copyToClipboard(view.context.getString(R.string.description), manga_summary.text.toString()) + } + + manga_genres_tags.setOnTagClickListener { tag -> performGlobalSearch(tag) } + + manga_cover.longClicks().subscribeUntilDestroy { + copyToClipboard(view.context.getString(R.string.title), presenter.manga.title) + } + } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -107,6 +153,7 @@ class MangaInfoController : NucleusController(), if (manga.initialized) { // Update view. setMangaInfo(manga, source) + } else { // Initialize manga. fetchMangaFromSource() @@ -122,19 +169,45 @@ class MangaInfoController : NucleusController(), private fun setMangaInfo(manga: Manga, source: Source?) { val view = view ?: return + //update full title TextView. + manga_full_title.text = if (manga.title.isBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.title + } + // Update artist TextView. - manga_artist.text = manga.artist + manga_artist.text = if (manga.artist.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.artist + } // Update author TextView. - manga_author.text = manga.author + manga_author.text = if (manga.author.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.author + } // If manga source is known update source TextView. - if (source != null) { - manga_source.text = source.toString() + manga_source.text = if(source == null) { + view.context.getString(R.string.unknown) + } else { + source.toString() } - // Update genres TextView. - manga_genres.text = manga.genre + // Update genres list + if(manga.genre.isNullOrBlank().not()){ + manga_genres_tags.setTags(manga.genre?.split(", ")) + } + + // Update description TextView. + manga_summary.text = if (manga.description.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.description + } // Update status TextView. manga_status.setText(when (manga.status) { @@ -144,9 +217,6 @@ class MangaInfoController : NucleusController(), else -> R.string.unknown }) - // Update description TextView. - manga_summary.text = manga.description - // Set the favorite drawable to the correct one. setFavoriteDrawable(manga.favorite) @@ -168,6 +238,11 @@ class MangaInfoController : NucleusController(), } } + override fun onDestroyView(view: View) { + manga_genres_tags.setOnTagClickListener(null) + super.onDestroyView(view) + } + /** * Update chapter count TextView. * @@ -177,6 +252,10 @@ class MangaInfoController : NucleusController(), manga_chapters?.text = DecimalFormat("#.#").format(count) } + fun setLastUpdateDate(date: Date){ + manga_last_update?.text = DateFormat.getDateInstance(DateFormat.SHORT).format(date) + } + /** * Toggles the favorite status and asks for confirmation to delete downloaded chapters. */ @@ -380,6 +459,35 @@ class MangaInfoController : NucleusController(), }) } + /** + * Copies a string to clipboard + * + * @param label Label to show to the user describing the content + * @param content the actual text to copy to the board + */ + private fun copyToClipboard(label: String, content: String){ + if(content.isBlank()) return + + val activity = activity ?: return + val view = view ?: return + + val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.primaryClip = ClipData.newPlainText(label, content) + + activity.toast( view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)), + Toast.LENGTH_SHORT) + } + + /** + * Perform a global search using the provided query. + * + * @param query the search query to pass to the search controller + */ + fun performGlobalSearch(query: String){ + val router = parentController?.router ?: return + router.pushController(CatalogueSearchController(query).withFadeTransaction()) + } + /** * Create shortcut using ShortcutManager. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index 2f97c7e682..9064c08a0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -18,6 +18,7 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.* /** * Presenter of MangaInfoFragment. @@ -28,6 +29,7 @@ class MangaInfoPresenter( val manga: Manga, val source: Source, private val chapterCountRelay: BehaviorRelay, + private val lastUpdateRelay: BehaviorRelay, private val mangaFavoriteRelay: PublishRelay, private val db: DatabaseHelper = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), @@ -37,7 +39,7 @@ class MangaInfoPresenter( /** * Subscription to send the manga to the view. */ - private var viewMangaSubcription: Subscription? = null + private var viewMangaSubscription: Subscription? = null /** * Subscription to update the manga from the source. @@ -56,14 +58,18 @@ class MangaInfoPresenter( mangaFavoriteRelay.observeOn(AndroidSchedulers.mainThread()) .subscribe { setFavorite(it) } .apply { add(this) } + + //update last update date + lastUpdateRelay.observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache(MangaInfoController::setLastUpdateDate) } /** * Sends the active manga to the view. */ fun sendMangaToView() { - viewMangaSubcription?.let { remove(it) } - viewMangaSubcription = Observable.just(manga) + viewMangaSubscription?.let { remove(it) } + viewMangaSubscription = Observable.just(manga) .subscribeLatestCache({ view, manga -> view.onNextManga(manga, source) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt index 9a997bae87..862514e96d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.util +import java.lang.Math.floor + /** * Replaces the given string to have at most [count] characters using [replacement] at its end. * If [replacement] is longer than [count] an exception will be thrown when `length > count`. @@ -11,3 +13,16 @@ fun String.chop(count: Int, replacement: String = "..."): String { this } + +/** + * Replaces the given string to have at most [count] characters using [replacement] near the center. + * If [replacement] is longer than [count] an exception will be thrown when `length > count`. + */ +fun String.truncateCenter(count: Int, replacement: String = "..."): String{ + if(length <= count) + return this + + val pieceLength:Int = floor((count - replacement.length).div(2.0)).toInt() + + return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }" +} \ No newline at end of file diff --git a/app/src/main/res/layout-land/manga_info_controller.xml b/app/src/main/res/layout-land/manga_info_controller.xml index ad371aaa8e..509e602663 100644 --- a/app/src/main/res/layout-land/manga_info_controller.xml +++ b/app/src/main/res/layout-land/manga_info_controller.xml @@ -59,6 +59,18 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label" + app:layout_constraintLeft_toRightOf="@+id/manga_source_label" + app:layout_constraintRight_toRightOf="parent" + android:layout_marginStart="8dp"/> + + + diff --git a/app/src/main/res/layout/catalogue_list_item.xml b/app/src/main/res/layout/catalogue_list_item.xml index d06caeffa0..40f52022fc 100644 --- a/app/src/main/res/layout/catalogue_list_item.xml +++ b/app/src/main/res/layout/catalogue_list_item.xml @@ -21,10 +21,11 @@ android:paddingTop="8dp" android:paddingBottom="8dp" android:layout_marginLeft="8dp"/> + + + - diff --git a/app/src/main/res/layout/chapters_item.xml b/app/src/main/res/layout/chapters_item.xml index 1b04056a84..6e9bc9eabe 100644 --- a/app/src/main/res/layout/chapters_item.xml +++ b/app/src/main/res/layout/chapters_item.xml @@ -41,10 +41,10 @@ android:layout_height="wrap_content" tools:text="22/02/2016" android:ellipsize="marquee" - android:maxLines="1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginLeft="16dp"/> + android:layout_marginLeft="16dp" + android:singleLine="true" /> + app:layout_constraintLeft_toLeftOf="parent" + android:singleLine="true" /> + + @@ -225,17 +240,16 @@ + app:layout_constraintTop_toBottomOf="@+id/guideline"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12f8044cf0..b16dd8bc48 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -300,12 +300,14 @@ Unknown Licensed Remove from library + Title Added to library Removed from library Author Artist Chapters Last chapter + Updated Status Source Genres @@ -319,6 +321,7 @@ Icon shape Failed to create shortcut! Delete downloaded chapters? + %1$s copied to clipboard Chapters @@ -457,4 +460,5 @@ Common Library Downloader +