diff --git a/app/build.gradle b/app/build.gradle
index 3ad59e14e..fe215f29f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,7 @@ import java.text.SimpleDateFormat
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'me.tatarka.retrolambda'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ed8a82fa7..2c5fc5bfc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -40,11 +40,10 @@
android:parentActivityName=".ui.main.MainActivity" >
-
0 && !this.mIsAnimatingOut && child!!.visibility == View.VISIBLE) {
+ // User scrolled down and the FAB is currently visible -> hide the FAB
+ animateOut(child)
+ } else if (dyConsumed < 0 && child!!.visibility != View.VISIBLE) {
+ // User scrolled up and the FAB is currently not visible -> show the FAB
+ animateIn(child)
+ }
+ }
+
+ open fun animateOut(button : FloatingActionButton) {}
+ open fun animateIn(button : FloatingActionButton) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt
new file mode 100644
index 000000000..347f9792c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt
@@ -0,0 +1,54 @@
+package eu.kanade.tachiyomi.ui.base.fab
+
+import android.content.Context
+import android.support.design.widget.FloatingActionButton
+import android.support.v4.view.animation.FastOutSlowInInterpolator
+import android.util.AttributeSet
+import android.view.View
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import eu.kanade.tachiyomi.R
+
+class FABAnimationUpDown() : FABAnimationBase()
+{
+ override var mIsAnimatingOut: Boolean = false
+ get() = super.mIsAnimatingOut
+
+ private val INTERPOLATOR = FastOutSlowInInterpolator()
+
+ /**
+ * Needed to prevent NoSuchMethodException
+ */
+ constructor(ctx: Context, attrs: AttributeSet) : this() { }
+
+ override fun animateOut(button: FloatingActionButton) {
+ super.animateIn(button)
+ val anim = AnimationUtils.loadAnimation(button.context, R.anim.fab_hide_to_bottom)
+ anim.interpolator = INTERPOLATOR
+ anim.duration = 200L
+ anim.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) {
+ mIsAnimatingOut = true
+ }
+
+ override fun onAnimationEnd(animation: Animation) {
+ mIsAnimatingOut = false
+ button.visibility = View.GONE
+ }
+
+ override fun onAnimationRepeat(animation: Animation) {
+ }
+ })
+ button.startAnimation(anim)
+
+ }
+
+ override fun animateIn(button: FloatingActionButton) {
+ super.animateOut(button)
+ button.visibility = View.VISIBLE
+ val anim = AnimationUtils.loadAnimation(button.context, R.anim.fab_show_from_bottom)
+ anim.duration = 200L
+ anim.interpolator = INTERPOLATOR
+ button.startAnimation(anim)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java
deleted file mode 100644
index cbcd0100c..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package eu.kanade.tachiyomi.ui.base.fab;
-
-import android.content.Context;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.FloatingActionButton;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import eu.kanade.tachiyomi.R;
-
-public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
- private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
- private boolean mIsAnimatingOut = false;
-
- public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
- super();
- }
-
- @Override
- public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View directTargetChild, final View target, final int nestedScrollAxes) {
- // Ensure we react to vertical scrolling
- return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
- || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
- }
-
- @Override
- public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View target, final int dxConsumed, final int dyConsumed,
- final int dxUnconsumed, final int dyUnconsumed) {
- super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
- if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
- // User scrolled down and the FAB is currently visible -> hide the FAB
- animateOut(child);
- } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
- // User scrolled up and the FAB is currently not visible -> show the FAB
- animateIn(child);
- }
- }
-
- // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
- private void animateOut(final FloatingActionButton button) {
- Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_hide_to_bottom);
- anim.setInterpolator(INTERPOLATOR);
- anim.setDuration(200L);
- anim.setAnimationListener(new Animation.AnimationListener() {
- public void onAnimationStart(Animation animation) {
- ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
- }
-
- public void onAnimationEnd(Animation animation) {
- ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
- button.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- button.startAnimation(anim);
- }
-
- // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
- private void animateIn(FloatingActionButton button) {
- button.setVisibility(View.VISIBLE);
- Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_show_from_bottom);
- anim.setDuration(200L);
- anim.setInterpolator(INTERPOLATOR);
- button.startAnimation(anim);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
new file mode 100644
index 000000000..346873539
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
@@ -0,0 +1,276 @@
+package eu.kanade.tachiyomi.ui.category
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.view.ActionMode
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.helper.ItemTouchHelper
+import android.view.Menu
+import android.view.MenuItem
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
+import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
+import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
+import kotlinx.android.synthetic.main.activity_edit_categories.*
+import kotlinx.android.synthetic.main.toolbar.*
+import nucleus.factory.RequiresPresenter
+
+
+/**
+ * Activity that shows categories.
+ * Uses R.layout.activity_edit_categories.
+ * UI related actions should be called from here.
+ */
+@RequiresPresenter(CategoryPresenter::class)
+class CategoryActivity : BaseRxActivity(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
+
+ /**
+ * Object used to show actionMode toolbar.
+ */
+ var actionMode: ActionMode? = null
+
+ /**
+ * Adapter containing category items.
+ */
+ private lateinit var adapter: CategoryAdapter
+
+ /**
+ * TouchHelper used for reorder animation and movement.
+ */
+ private lateinit var touchHelper: ItemTouchHelper
+
+ companion object {
+ /**
+ * Create new CategoryActivity intent.
+ *
+ * @param context context information.
+ */
+ @JvmStatic
+ fun newIntent(context: Context): Intent? {
+ return Intent(context, CategoryActivity::class.java)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Inflate activity_edit_categories.xml.
+ setContentView(R.layout.activity_edit_categories)
+
+ // Setup the toolbar.
+ setupToolbar(toolbar)
+
+ // Get new adapter.
+ adapter = CategoryAdapter(this)
+
+ // Create view and inject category items into view
+ recycler.layoutManager = LinearLayoutManager(this)
+ recycler.setHasFixedSize(true)
+ recycler.adapter = adapter
+
+ // Touch helper to drag and reorder categories
+ touchHelper = ItemTouchHelper(CategoryItemTouchHelper(adapter))
+ touchHelper.attachToRecyclerView(recycler)
+
+ // Create OnClickListener for creating new category
+ fab.setOnClickListener({ v ->
+ MaterialDialog.Builder(this)
+ .title(R.string.action_add_category)
+ .negativeText(R.string.button_cancel)
+ .input(R.string.name, 0, false)
+ { dialog, input -> presenter.createCategory(input.toString()) }
+ .show()
+ })
+ }
+
+ /**
+ * Finishes action mode.
+ * Call this when action mode action is finished.
+ */
+ fun destroyActionModeIfNeeded() {
+ actionMode?.finish()
+ }
+
+ /**
+ * Fill adapter with category items
+ *
+ * @param categories list containing categories
+ */
+ fun setCategories(categories: List) {
+ destroyActionModeIfNeeded()
+ adapter.setItems(categories)
+ }
+
+ /**
+ * Delete selected categories
+ *
+ * @param categories list containing categories
+ */
+ private fun deleteCategories(categories: List?) {
+ presenter.deleteCategories(categories)
+ }
+
+ /**
+ * Returns the selected categories
+ *
+ * @return list of selected categories
+ */
+ private fun getSelectedCategories(): List? {
+ // Create a list of the selected categories
+ return adapter.selectedItems.map { adapter.getItem(it) }
+ }
+
+ /**
+ * Show MaterialDialog which let user change category name.
+ *
+ * @param category category that will be edited.
+ */
+ private fun editCategory(category: Category?) {
+ MaterialDialog.Builder(this)
+ .title(R.string.action_rename_category)
+ .negativeText(R.string.button_cancel)
+ .onNegative { materialDialog, dialogAction -> destroyActionModeIfNeeded() }
+ .input(getString(R.string.name), category?.name, false)
+ { dialog, input -> presenter.renameCategory(category as Category, input.toString()) }
+ .show()
+ }
+
+ /**
+ * Toggle actionMode selection
+ *
+ * @param position position of selected item
+ */
+ private fun toggleSelection(position: Int) {
+ adapter.toggleSelection(position, false)
+
+ // Get selected item count
+ val count = adapter.selectedItemCount
+
+ // If no item is selected finish action mode
+ if (count == 0) {
+ actionMode?.finish()
+ } else {
+ // This block will only run if actionMode is not null
+ actionMode?.let {
+
+ // Set title equal to selected item
+ it.title = getString(R.string.label_selected, count)
+ it.invalidate()
+
+ // Show edit button only when one item is selected
+ val editItem = it.menu?.findItem(R.id.action_edit)
+ editItem?.isVisible = count == 1
+ }
+ }
+ }
+
+ /**
+ * Called each time the action mode is shown.
+ * Always called after onCreateActionMode
+ *
+ * @return false
+ */
+ override fun onPrepareActionMode(p0: ActionMode?, p1: Menu?): Boolean {
+ return false
+ }
+
+ /**
+ * Called when action mode item clicked.
+ *
+ * @param actionMode action mode toolbar.
+ * @param menuItem selected menu item.
+ *
+ * @return action mode item clicked exist status
+ */
+ override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
+ when (menuItem.itemId) {
+ R.id.action_delete -> {
+ // Delete select categories.
+ deleteCategories(getSelectedCategories())
+ return true
+ }
+ R.id.action_edit -> {
+ // Edit selected category
+ editCategory(getSelectedCategories()?.get(0))
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Inflate menu when action mode selected.
+ *
+ * @param mode ActionMode object
+ * @param menu Menu object
+ *
+ * @return true
+ */
+ override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+ // Inflate menu.
+ mode.menuInflater.inflate(R.menu.category_selection, menu)
+ // Enable adapter multi selection.
+ adapter.mode = LibraryCategoryAdapter.MODE_MULTI
+ return true
+ }
+
+ /**
+ * Called when action mode destroyed.
+ *
+ * @param mode ActionMode object.
+ */
+ override fun onDestroyActionMode(mode: ActionMode?) {
+ // Reset adapter to single selection
+ adapter.mode = LibraryCategoryAdapter.MODE_SINGLE
+ // Clear selected items
+ adapter.clearSelection()
+ actionMode = null
+ }
+
+ /**
+ * Called when item in list is clicked.
+ *
+ * @param position position of clicked item.
+ */
+ override fun onListItemClick(position: Int): Boolean {
+ // Check if action mode is initialized and selected item exist.
+ if (actionMode != null && position != -1) {
+ // Toggle selection of clicked item.
+ toggleSelection(position)
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * Called when item long clicked
+ *
+ * @param position position of clicked item.
+ */
+ override fun onListItemLongClick(position: Int) {
+ // Check if action mode is initialized.
+ if (actionMode == null)
+ // Initialize action mode
+ actionMode = startSupportActionMode(this)
+
+ // Set item as selected
+ toggleSelection(position)
+ }
+
+ /**
+ * Called when item is dragged
+ *
+ * @param viewHolder view that contains dragged item
+ */
+ override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) {
+ // Notify touchHelper
+ touchHelper.startDrag(viewHolder)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt
new file mode 100644
index 000000000..a377276a1
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt
@@ -0,0 +1,110 @@
+package eu.kanade.tachiyomi.ui.category
+
+import android.view.ViewGroup
+import com.amulyakhare.textdrawable.util.ColorGenerator
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
+import eu.kanade.tachiyomi.util.inflate
+import java.util.*
+
+/**
+ * Adapter of CategoryHolder.
+ * Connection between Activity and Holder
+ * Holder updates should be called from here.
+ *
+ * @param activity activity that created adapter
+ * @constructor Creates a CategoryAdapter object
+ */
+class CategoryAdapter(private val activity: CategoryActivity) : FlexibleAdapter(), ItemTouchHelperAdapter {
+
+ /**
+ * Generator used to generate circle letter icons
+ */
+ private val generator: ColorGenerator
+
+ init {
+ // Let generator use Material Design colors.
+ // Material design is love, material design is live!
+ generator = ColorGenerator.MATERIAL
+
+ // Set unique id's
+ setHasStableIds(true)
+ }
+
+ /**
+ * Called when ViewHolder is created
+ *
+ * @param parent parent View
+ * @param viewType int containing viewType
+ */
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
+ // Inflate layout with item_edit_categories.xml
+ val view = parent.inflate(R.layout.item_edit_categories)
+ return CategoryHolder(view, this, activity, activity)
+ }
+
+ /**
+ * Called when ViewHolder is bind
+ *
+ * @param holder bind holder
+ * @param position position of holder
+ */
+ override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
+ // Update holder values.
+ val category = getItem(position)
+ holder.onSetValues(category, generator)
+
+ //When user scrolls this bind the correct selection status
+ holder.itemView.isActivated = isSelected(position)
+ }
+
+ /**
+ * Update items with list of categories
+ *
+ * @param items list of categories
+ */
+ fun setItems(items: List) {
+ mItems = ArrayList(items)
+ notifyDataSetChanged()
+ }
+
+ /**
+ * Get category by position
+ *
+ * @param position position of item
+ */
+ override fun getItemId(position: Int): Long {
+ return mItems[position].id!!.toLong()
+ }
+
+ /**
+ * Called when item is moved
+ *
+ * @param fromPosition previous position of item.
+ * @param toPosition new position of item.
+ */
+ override fun onItemMove(fromPosition: Int, toPosition: Int) {
+ // Move items and notify touch helper
+ Collections.swap(mItems, fromPosition, toPosition)
+ notifyItemMoved(fromPosition, toPosition)
+
+ // Update database
+ activity.presenter.reorderCategories(mItems)
+ }
+
+ /**
+ * Must be implemented, not used
+ */
+ override fun onItemDismiss(position: Int) {
+ // Empty method.
+ }
+
+ /**
+ * Must be implemented, not used
+ */
+ override fun updateDataSet(p0: String?) {
+ // Empty method.
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt
new file mode 100644
index 000000000..f4cce6540
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt
@@ -0,0 +1,74 @@
+package eu.kanade.tachiyomi.ui.category
+
+import android.graphics.Color
+import android.graphics.Typeface
+import android.support.v4.view.MotionEventCompat
+import android.view.MotionEvent
+import android.view.View
+import com.amulyakhare.textdrawable.TextDrawable
+import com.amulyakhare.textdrawable.util.ColorGenerator
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
+import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
+import kotlinx.android.synthetic.main.item_edit_categories.view.*
+
+/**
+ * Holder that contains category item.
+ * Uses R.layout.item_edit_categories.
+ * UI related actions should be called from here.
+ *
+ * @param view view of category item.
+ * @param adapter adapter belonging to holder.
+ * @param listener called when item clicked.
+ * @param dragListener called when item dragged.
+ *
+ * @constructor Create CategoryHolder object
+ */
+class CategoryHolder(view: View, adapter: CategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener, dragListener: OnStartDragListener) : FlexibleViewHolder(view, adapter, listener) {
+
+ init {
+ // Create round letter image onclick to simulate long click
+ itemView.image.setOnClickListener({ v ->
+ // Simulate long click on this view to enter selection mode
+ onLongClick(view)
+ })
+
+ // Set on touch listener for reorder image
+ itemView.reorder.setOnTouchListener({ v, event ->
+ if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ dragListener.onStartDrag(this)
+ }
+ false
+ })
+ }
+
+ /**
+ * Update category item values.
+ *
+ * @param category category of item.
+ * @param generator generator used to generate circle letter icons.
+ */
+ fun onSetValues(category: Category, generator: ColorGenerator) {
+ // Set capitalized title.
+ itemView.title.text = category.name.capitalize()
+
+ // Update circle letter image.
+ itemView.image.setImageDrawable(getRound(category.name.substring(0, 1).toUpperCase(), generator))
+ }
+
+ /**
+ * Returns circle letter image
+ *
+ * @param text first letter of string
+ * @param generator the generator used to generate circle letter image
+ */
+ private fun getRound(text: String, generator: ColorGenerator): TextDrawable {
+ return TextDrawable.builder()
+ .beginConfig()
+ .textColor(Color.WHITE)
+ .useFont(Typeface.DEFAULT)
+ .toUpperCase()
+ .endConfig()
+ .buildRound(text, generator.getColor(text))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt
new file mode 100644
index 000000000..1e1ad8df3
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt
@@ -0,0 +1,26 @@
+package eu.kanade.tachiyomi.ui.category
+
+import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
+import eu.kanade.tachiyomi.ui.base.adapter.SimpleItemTouchHelperCallback
+
+class CategoryItemTouchHelper(adapter: ItemTouchHelperAdapter) : SimpleItemTouchHelperCallback(adapter) {
+
+ /**
+ * Disable items swipe remove
+ *
+ * @return false
+ */
+ override fun isItemViewSwipeEnabled(): Boolean {
+ return false
+ }
+
+ /**
+ * Disable long press item drag
+ *
+ * @return false
+ */
+ override fun isLongPressDragEnabled(): Boolean {
+ return false
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt
new file mode 100644
index 000000000..9a67159eb
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt
@@ -0,0 +1,106 @@
+package eu.kanade.tachiyomi.ui.category
+
+import android.os.Bundle
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import rx.android.schedulers.AndroidSchedulers
+import javax.inject.Inject
+
+/**
+ * Presenter of CategoryActivity.
+ * Contains information and data for activity.
+ * Observable updates should be called from here.
+ */
+class CategoryPresenter : BasePresenter() {
+
+ /**
+ * Used to connect to database
+ */
+ @Inject lateinit var db: DatabaseHelper
+
+ /**
+ * List containing categories
+ */
+ private var categories: List? = null
+
+ companion object {
+ /**
+ * The id of the restartable.
+ */
+ final private val GET_CATEGORIES = 1
+ }
+
+ override fun onCreate(savedState: Bundle?) {
+ super.onCreate(savedState)
+
+ // Get categories as list
+ restartableLatestCache(GET_CATEGORIES,
+ {
+ db.categories.asRxObservable()
+ .doOnNext { categories -> this.categories = categories }
+ .observeOn(AndroidSchedulers.mainThread())
+ }, CategoryActivity::setCategories)
+
+ // Start get categories as list task
+ start(GET_CATEGORIES)
+ }
+
+
+ /**
+ * Create category and add it to database
+ *
+ * @param name name of category
+ */
+ fun createCategory(name: String) {
+ // Create category.
+ val cat = Category.create(name)
+
+ // Set the new item in the last position.
+ var max = 0
+ if (categories != null) {
+ for (cat2 in categories!!) {
+ if (cat2.order > max) {
+ max = cat2.order + 1
+ }
+ }
+ }
+ cat.order = max
+
+ // Insert into database.
+ db.insertCategory(cat).asRxObservable().subscribe()
+ }
+
+ /**
+ * Delete category from database
+ *
+ * @param categories list of categories
+ */
+ fun deleteCategories(categories: List?) {
+ db.deleteCategories(categories).asRxObservable().subscribe()
+ }
+
+ /**
+ * Reorder categories in database
+ *
+ * @param categories list of categories
+ */
+ fun reorderCategories(categories: List) {
+ for (i in categories.indices) {
+ categories[i].order = i
+ }
+
+ db.insertCategories(categories).asRxObservable().subscribe()
+ }
+
+ /**
+ * Rename a category
+ *
+ * @param category category that gets renamed
+ * @param name new name of category
+ */
+ fun renameCategory(category: Category, name: String) {
+ category.name = name
+ db.insertCategory(category).asRxObservable().subscribe()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
index 217600448..e397f2045 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
@@ -38,7 +38,7 @@ import eu.kanade.tachiyomi.data.io.IOHandler;
import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
import eu.kanade.tachiyomi.event.LibraryMangasEvent;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
-import eu.kanade.tachiyomi.ui.library.category.CategoryActivity;
+import eu.kanade.tachiyomi.ui.category.CategoryActivity;
import eu.kanade.tachiyomi.ui.main.MainActivity;
import eu.kanade.tachiyomi.util.ToastUtil;
import icepick.State;
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java
deleted file mode 100644
index b5409cde5..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.category;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.design.widget.FloatingActionButton;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v7.view.ActionMode;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.Toolbar;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.afollestad.materialdialogs.MaterialDialog;
-
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity;
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
-import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener;
-import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
-import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter;
-import nucleus.factory.RequiresPresenter;
-import rx.Observable;
-
-@RequiresPresenter(CategoryPresenter.class)
-public class CategoryActivity extends BaseRxActivity implements
- ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
-
- @Bind(R.id.toolbar) Toolbar toolbar;
- @Bind(R.id.categories_list) RecyclerView recycler;
- @Bind(R.id.fab) FloatingActionButton fab;
-
- private CategoryAdapter adapter;
- private ActionMode actionMode;
- private ItemTouchHelper touchHelper;
-
- public static Intent newIntent(Context context) {
- return new Intent(context, CategoryActivity.class);
- }
-
- @Override
- protected void onCreate(Bundle savedState) {
- super.onCreate(savedState);
- setContentView(R.layout.activity_edit_categories);
- ButterKnife.bind(this);
-
- setupToolbar(toolbar);
-
- adapter = new CategoryAdapter(this);
- recycler.setLayoutManager(new LinearLayoutManager(this));
- recycler.setHasFixedSize(true);
- recycler.setAdapter(adapter);
- recycler.addItemDecoration(new DividerItemDecoration(
- ResourcesCompat.getDrawable(getResources(), R.drawable.line_divider, null)));
-
- // Touch helper to drag and reorder categories
- touchHelper = new ItemTouchHelper(new CategoryItemTouchHelper(adapter));
- touchHelper.attachToRecyclerView(recycler);
-
- fab.setOnClickListener(v -> {
- new MaterialDialog.Builder(this)
- .title(R.string.action_add_category)
- .input(R.string.name, 0, false, (dialog, input) -> {
- getPresenter().createCategory(input.toString());
- })
- .show();
- });
- }
-
- public void setCategories(List categories) {
- destroyActionModeIfNeeded();
- adapter.setItems(categories);
- }
-
- private List getSelectedCategories() {
- // Create a blocking copy of the selected categories
- return Observable.from(adapter.getSelectedItems())
- .map(adapter::getItem).toList().toBlocking().single();
- }
-
- @Override
- public boolean onListItemClick(int position) {
- if (actionMode != null && position != -1) {
- toggleSelection(position);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void onListItemLongClick(int position) {
- if (actionMode == null)
- actionMode = startSupportActionMode(this);
-
- toggleSelection(position);
- }
-
- private void toggleSelection(int position) {
- adapter.toggleSelection(position, false);
-
- int count = adapter.getSelectedItemCount();
- if (count == 0) {
- actionMode.finish();
- } else {
- setContextTitle(count);
- actionMode.invalidate();
- MenuItem editItem = actionMode.getMenu().findItem(R.id.action_edit);
- editItem.setVisible(count == 1);
- }
- }
-
- private void setContextTitle(int count) {
- actionMode.setTitle(getString(R.string.label_selected, count));
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- mode.getMenuInflater().inflate(R.menu.category_selection, menu);
- adapter.setMode(LibraryCategoryAdapter.MODE_MULTI);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_delete:
- deleteCategories(getSelectedCategories());
- return true;
- case R.id.action_edit:
- editCategory(getSelectedCategories().get(0));
- return true;
- }
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- adapter.setMode(LibraryCategoryAdapter.MODE_SINGLE);
- adapter.clearSelection();
- actionMode = null;
- }
-
- public void destroyActionModeIfNeeded() {
- if (actionMode != null) {
- actionMode.finish();
- }
- }
-
- private void deleteCategories(List categories) {
- getPresenter().deleteCategories(categories);
- }
-
- private void editCategory(Category category) {
- new MaterialDialog.Builder(this)
- .title(R.string.action_rename_category)
- .input(getString(R.string.name), category.name, false, (dialog, input) -> {
- getPresenter().renameCategory(category, input.toString());
- })
- .show();
- }
-
- @Override
- public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
- touchHelper.startDrag(viewHolder);
- }
-
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java
deleted file mode 100644
index e1a845385..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.category;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.amulyakhare.textdrawable.util.ColorGenerator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter;
-
-public class CategoryAdapter extends FlexibleAdapter implements
- ItemTouchHelperAdapter {
-
- private final CategoryActivity activity;
- private final ColorGenerator generator;
-
- public CategoryAdapter(CategoryActivity activity) {
- this.activity = activity;
- generator = ColorGenerator.DEFAULT;
- setHasStableIds(true);
- }
-
- public void setItems(List items) {
- mItems = new ArrayList<>(items);
- notifyDataSetChanged();
- }
-
- @Override
- public long getItemId(int position) {
- return mItems.get(position).id;
- }
-
- @Override
- public void updateDataSet(String param) {
-
- }
-
- @Override
- public CategoryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- LayoutInflater inflater = activity.getLayoutInflater();
- View v = inflater.inflate(R.layout.item_edit_categories, parent, false);
- return new CategoryHolder(v, this, activity, activity);
- }
-
- @Override
- public void onBindViewHolder(CategoryHolder holder, int position) {
- final Category category = getItem(position);
- holder.onSetValues(category, generator);
-
- //When user scrolls this bind the correct selection status
- holder.itemView.setActivated(isSelected(position));
- }
-
- @Override
- public void onItemMove(int fromPosition, int toPosition) {
- if (fromPosition < toPosition) {
- for (int i = fromPosition; i < toPosition; i++) {
- Collections.swap(mItems, i, i + 1);
- }
- } else {
- for (int i = fromPosition; i > toPosition; i--) {
- Collections.swap(mItems, i, i - 1);
- }
- }
-
- activity.getPresenter().reorderCategories(mItems);
- }
-
- @Override
- public void onItemDismiss(int position) {
-
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java
deleted file mode 100644
index feec63a71..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.category;
-
-import android.support.v4.view.MotionEventCompat;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.amulyakhare.textdrawable.TextDrawable;
-import com.amulyakhare.textdrawable.util.ColorGenerator;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import butterknife.OnClick;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
-import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener;
-
-public class CategoryHolder extends FlexibleViewHolder {
-
- private View view;
-
- @Bind(R.id.image) ImageView image;
- @Bind(R.id.title) TextView title;
- @Bind(R.id.reorder) ImageView reorder;
-
- public CategoryHolder(View view, CategoryAdapter adapter,
- OnListItemClickListener listener, OnStartDragListener dragListener) {
- super(view, adapter, listener);
- ButterKnife.bind(this, view);
- this.view = view;
-
- reorder.setOnTouchListener((v, event) -> {
- if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
- dragListener.onStartDrag(this);
- return true;
- }
- return false;
- });
- }
-
- public void onSetValues(Category category, ColorGenerator generator) {
- title.setText(category.name);
- image.setImageDrawable(getRound(category.name.substring(0, 1), generator));
- }
-
- private TextDrawable getRound(String text, ColorGenerator generator) {
- return TextDrawable.builder().buildRound(text, generator.getColor(text));
- }
-
- @OnClick(R.id.image)
- void onImageClick() {
- // Simulate long click on this view to enter selection mode
- onLongClick(view);
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java
deleted file mode 100644
index 7e38a25de..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.category;
-
-import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter;
-import eu.kanade.tachiyomi.ui.base.adapter.SimpleItemTouchHelperCallback;
-
-public class CategoryItemTouchHelper extends SimpleItemTouchHelperCallback {
-
- public CategoryItemTouchHelper(ItemTouchHelperAdapter adapter) {
- super(adapter);
- }
-
- @Override
- public boolean isItemViewSwipeEnabled() {
- return false;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java
deleted file mode 100644
index edb305c9a..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.category;
-
-import android.os.Bundle;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-import eu.kanade.tachiyomi.data.database.DatabaseHelper;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
-import rx.android.schedulers.AndroidSchedulers;
-
-public class CategoryPresenter extends BasePresenter {
-
- @Inject DatabaseHelper db;
-
- private List categories;
-
- private static final int GET_CATEGORIES = 1;
-
- @Override
- protected void onCreate(Bundle savedState) {
- super.onCreate(savedState);
-
- restartableLatestCache(GET_CATEGORIES,
- () -> db.getCategories().asRxObservable()
- .doOnNext(categories -> this.categories = categories)
- .observeOn(AndroidSchedulers.mainThread()),
- CategoryActivity::setCategories);
-
- start(GET_CATEGORIES);
- }
-
- public void createCategory(String name) {
- Category cat = Category.create(name);
-
- // Set the new item in the last position
- int max = 0;
- if (categories != null) {
- for (Category cat2 : categories) {
- if (cat2.order > max) {
- max = cat2.order + 1;
- }
- }
- }
- cat.order = max;
-
- db.insertCategory(cat).asRxObservable().subscribe();
- }
-
- public void deleteCategories(List categories) {
- db.deleteCategories(categories).asRxObservable().subscribe();
- }
-
- public void reorderCategories(List categories) {
- for (int i = 0; i < categories.size(); i++) {
- categories.get(i).order = i;
- }
-
- db.insertCategories(categories).asRxObservable().subscribe();
- }
-
- public void renameCategory(Category category, String name) {
- category.name = name;
- db.insertCategory(category).asRxObservable().subscribe();
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
new file mode 100644
index 000000000..377fd1bf5
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
@@ -0,0 +1,15 @@
+package eu.kanade.tachiyomi.util
+
+import android.support.annotation.LayoutRes
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+/**
+ * Extension method to inflate a view directly from its parent.
+ * @param layout the layout to inflate.
+ * @param attachToRoot whether to attach the view to the root or not. Defaults to false.
+ */
+fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View {
+ return LayoutInflater.from(context).inflate(layout, this, attachToRoot)
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_action_reorder.png b/app/src/main/res/drawable-hdpi/ic_action_reorder.png
new file mode 100644
index 000000000..9aa1825a0
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_reorder.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index 29a4975ff..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index b5d38304b..000000000
Binary files a/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_reorder.png b/app/src/main/res/drawable-mdpi/ic_action_reorder.png
new file mode 100644
index 000000000..9a7eab26f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_reorder.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index 3dbdf4e6c..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xhdpi/ic_action_reorder.png
new file mode 100644
index 000000000..7e448783e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_reorder.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index f36c16996..000000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png
new file mode 100644
index 000000000..45c2557b5
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index 4c72a1429..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png
new file mode 100644
index 000000000..a6a017b43
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png
deleted file mode 100644
index 525ebee16..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ
diff --git a/app/src/main/res/layout/activity_edit_categories.xml b/app/src/main/res/layout/activity_edit_categories.xml
index 4e8b4af20..5f044468f 100644
--- a/app/src/main/res/layout/activity_edit_categories.xml
+++ b/app/src/main/res/layout/activity_edit_categories.xml
@@ -4,6 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
android:gravity="center">
@@ -12,9 +13,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
- android:id="@+id/categories_list"
+ android:id="@+id/recycler"
android:choiceMode="multipleChoice"
- android:listSelector="@color/list_choice_pressed_bg_light" />
+ android:listSelector="@color/list_choice_pressed_bg_light"
+ tools:listitem="@layout/item_edit_categories"/>
+ app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.FABAnimationUpDown"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_edit_categories.xml b/app/src/main/res/layout/item_edit_categories.xml
index 97457cf7a..a4b1a6c3d 100644
--- a/app/src/main/res/layout/item_edit_categories.xml
+++ b/app/src/main/res/layout/item_edit_categories.xml
@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightLarge"
- android:paddingTop="@dimen/margin_top"
- android:paddingBottom="@dimen/margin_bottom"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
android:background="@drawable/selector_chapter_light">
+ android:layout_alignParentEnd="true"
+ android:src="@drawable/ic_action_reorder"/>