Verified Commit eefe4f4c authored by Ignacio García's avatar Ignacio García 🇯🇵
Browse files

Initial Release.

parents
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
# Animated Level List
This library aims to make easier creating lists of objects organized in levels, like folders and sub-folders, for example.
## Usage for simple lists
This usage description is useful for those cases when the level list we want to show only needs to show a String, like item name.
For any other cases please see next Usage explanations.
**First**, create your model class implementing PlainItem.
Example:
```kotlin
class Folder(override var itemName: String/*,...*/): PlainItem {
//...
}
```
**Second**, create your `DefaultLevelListListAdapter` or `DefaultLevelListRVRendererAdapter` and set it to your RecyclerView.
You can use the base implementation of `LevelListBuilder`, `LevelListBuilderImpl`.
Example with `DefaultLevelListListAdapter`:
```kotlin
adapter = BaseLevelListListAdapter<Folder>(this, LevelListBuilderImpl("/", 10), 16f)
recyclerView.adapter = adapter.adapter
adapter.submitPlainList(plainFolderList)
```
Example with `DefaultLevelListRVRendererAdapter`:
```kotlin
adapter = BaseLevelListRVRendererAdapter<Folder>(this, LevelListBuilderImpl("/", 10), 16f)
recyclerView.adapter = adapter.adapter
adapter.submitPlainList(plainFolderList)
```
**Third**, implement listener method. Example:
```kotlin
override fun onItemClicked(item: LevelListItem) {
val folder = item.item
Toast.makeText(context, "Item ${folder.itemName} was clicked!", Toast.LENGTH_SHORT).show()
}
```
And that's it!
______________________
## Usage (Example using ViewHolder)
**First**, create your model class implementing PlainItem.
Example:
```kotlin
class Folder(override var itemName: String/*,...*/): PlainItem {
//...
}
```
**Second**, create your ViewHolder implementing `LevelItemViewHolder`.
Do not forget to override methods `bind`, `animateToggleChildren` (unless ViewHolder is specific for leaf items, with no children),
`toggleChildrenFinished`. If you extend `DefaultLevelItemViewHolder` instead, there is no need to create`differentiateParentOrChildDisplay` and
`differentiateUnfoldedCondition`, just use them as they are or override them if needed.
Example:
```kotlin
class FolderViewHolder(
rootView: View
) : LevelItemViewHolder(
rootView
) {
override fun bind() {
differentiateParentOrChildDisplay()
differentiateUnfoldedCondition()
rootView.showchildrenbutton.rotation = 0f
rootView.experimentNameTV.text = item.levelListItemName
// ... assign other properties from item to rootView elements
rootView.showchildrenclicker.setOnClickListener {
view ->
view.isClickable = false
toggleChildren(view, item)
}
}
override fun animateToggleChildren(areChildrenUnfolded: Boolean) {
rotateArrow(areChildrenUnfolded)
}
override fun toggleChildrenFinished() {
super.toggleChildrenFinished()
rootView.showchildrenclicker.isClickable = true
}
private fun differentiateParentOrChildDisplay() {
rootView.showchildrenbutton.visibility = if(item.children.isEmpty()) View.GONE else View.VISIBLE
rootView.showchildrenclicker.visibility = if(item.children.isEmpty()) View.GONE else View.VISIBLE
}
private fun differentiateUnfoldedCondition() {
rootView.showchildrenbutton.rotation =
if(!item.areChildrenUnfolded) 0f
else 90f
}
private fun rotateArrow(areChildrenUnfolded: Boolean) {
rootView.showchildrenbutton.animate().rotation(
if(areChildrenUnfolded) 0f
else 90f
).setDuration(200)
.withEndAction {
toggleChildrenFinished()
}
}
}
```
**Third**, create your adapter implementing `LevelListListAdapter`. Do not forget to
implement `createCustomViewHolder` method. You can optionally implement `filterCriteria` method
if you wish to filter `RecyclerView` results against something more / else than just the itemName (working by default).
Example:
```kotlin
class FolderAdapter(
levelListActionListener: LevelItemActionListener,
levelListBuilder: LevelListBuilderImpl<Folder>,
indent: Float
) : LevelListListAdapter<Folder>(
levelListActionListener, levelListBuilder, indent
) {
override fun createCustomViewHolder(parent: ViewGroup, viewType: Int): LevelItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.experiment_row, parent, false)
return FolderViewHolder(view)
}
override fun filterCriteria(item: T, constraint: CharSequence): Boolean {
return item.itemName.contains(constraint, true)
}
}
```
In the case you have a specific ViewHolder for leaf items (with no children), the only thing changing is
`createCustomViewHolder` method. Example:
```kotlin
class FolderListAdapter(
levelListActionListener: LevelItemActionListener,
levelListBuilder: LevelListBuilderImpl<Folder>,
indent: Float
) : LevelListListAdapter<Folder>(
levelListActionListener, levelListBuilder, indent
) {
override fun createCustomViewHolder(parent: ViewGroup, viewType: Int): LevelItemViewHolder {
return when(viewType) {
PARENT_VIEW -> {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.my_parent_item_row_layout, parent, false
)
MyParentFolderViewHolder(view)
}
else -> {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.my_item_row_layout, parent, false // this layout disallows children from UI, for example, does not contain button for toggling children
)
MyLeafFolderViewHolder(view)
}
}
}
}
```
Example of implementation of a leaf specific ViewHolder (as you can see it is pretty simple):
```kotlin
open class MyLeafFolderViewHolder(
rootView: View
) : LevelItemViewHolder(
rootView
) {
override fun bind() {
rootView.foldernameTV.text = item.levelListItemName
// ... assign other properties from item to rootView elements
}
}
```
Example of implementation of the no-leaf or parent specific ViewHolder
```kotlin
class MyParentFolderViewHolder(
rootView: View
) : MyLeafFolderViewHolder(rootView)
{
override fun bind() {
rootView.showchildrenbutton.rotation = 0f
super.bind()
rootView.showchildrenclicker.setOnClickListener {
view ->
view.isClickable = false
toggleChildren(view, item)
}
}
override fun animateToggleChildren(areChildrenUnfolded: Boolean) {
rotateArrow(areChildrenUnfolded)
}
override fun toggleChildrenFinished() {
super.toggleChildrenFinished()
rootView.showchildrenclicker.isClickable = true
}
private fun rotateArrow(areChildrenUnfolded: Boolean) {
rootView.showchildrenbutton.animate().rotation(
if(areChildrenUnfolded) 0f
else 90f
).setDuration(200)
.withEndAction {
toggleChildrenFinished()
}
}
}
```
**Fourth**, create your level list Adapter and set it to your RecyclerView.
You can use the default implementation of `LevelListBuilder`, `LevelListBuilderImpl`.
Example:
```kotlin
adapter = FolderListAdapter(this, LevelListBuilderImpl("/", 10), 16f)
recyclerView.adapter = adapter.adapter
adapter.submitPlainList(plainFolderList)
```
**Fifth**, implement listener method. Example:
```kotlin
override fun onItemClicked(item: LevelListItem) {
val folder = item.item
Toast.makeText(context, "Item ${folder.itemName} was clicked!", Toast.LENGTH_SHORT).show()
}
```
And that's it!
______________________
## Usage (Example using Renderer)
**First**, create your model class implementing PlainItem.
Example:
```kotlin
class Folder(override var itemName: String): PlainItem {
//...
}
```
**Second**, create your Renderer implementing `LevelListItemRenderer`.
Do not forget to override methods `bind`, `animateToggleChildren` (unless Renderer is specific for leaf items, with no children),
`toggleChildrenFinished`, `hookListeners`. If you extend `DefaultLevelItemRenderer` instead, there is no need to create`differentiateParentOrChildDisplay` and
`differentiateUnfoldedCondition`, just use them as they are or override them if needed.
Example:
```kotlin
class FolderRenderer: LevelListItemRenderer() {
override fun bind() {
differentiateParentOrChildDisplay()
differentiateUnfoldedCondition()
rootView.foldernameTV.text = content.levelListItemName
// ... assign other properties from item to rootView elements
}
override fun hookListeners(rootView: View) {
super.hookListeners(rootView)
rootView.showchildrenbutton.visibility = View.VISIBLE
rootView.showchildrenclicker.setOnClickListener { view ->
view.isClickable = false
toggleChildren(view, content)
}
}
private fun differentiateParentOrChildDisplay() {
rootView.showchildrenbutton.visibility = if(content.children.isEmpty()) View.GONE else View.VISIBLE
rootView.showchildrenclicker.visibility = if(content.children.isEmpty()) View.GONE else View.VISIBLE
}
private fun differentiateUnfoldedCondition() {
showChildrenButton.rotation =
if(!content.areChildrenUnfolded) 0f
else 90f
}
override fun inflate(inflater: LayoutInflater?, parent: ViewGroup?): View {
return inflater!!.inflate(R.layout.my_parent_item_row_layout, parent, false)
}
override fun animateToggleChildren(areChildrenUnfolded: Boolean) {
rotateArrow(areChildrenUnfolded)
}
override fun toggleChildrenFinished() {
super.toggleChildrenFinished()
rootView.showchildrenclicker.isClickable = true
}
private fun rotateArrow(areChildrenUnfolded: Boolean) {
rootView.showchildrenbutton.animate().rotation(
if(areChildrenUnfolded) 0f
else 90f
).setDuration(200)
.withEndAction {
toggleChildrenFinished()
}
}
}
```
**Third**, create your adapter implementing `LevelListRVRendererAdapter`. There is no need to implement any methods.
You can optionally implement `filterCriteria` method if you wish to filter `RecyclerView` results against something more / else than just the itemName (working by default).
Example:
```kotlin
class FolderRVRendererAdapter(
levelItemActionListener: LevelItemActionListener,
levelListBuilder: LevelListBuilder<JustNameFolder>,
indent: Float
): LevelListRVRendererAdapter<JustNameFolder>(
levelItemActionListener,
levelListBuilder,
indent,
RendererBuilder(FolderRenderer())
) {
override fun filterCriteria(item: T, constraint: CharSequence): Boolean {
return item.itemName.contains(constraint, true)
}
}
```
In the case you have a specific Renderer for leaf items (with no children), the only thing changing is
that RendererBuilder needs to be overridden. Example:
```kotlin
class FolderRendererBuilder : RendererBuilder<LevelListItem>() {
init {
val prototypes = createPrototypes()
setPrototypes(prototypes)
}
override fun getPrototypeClass(content: LevelListItem): Class<*> {
return if (content.children.isEmpty()) MyLeafFolderRenderer::class.java
else MyParentFoldermRenderer::class.java
}
private fun createPrototypes(): List<Renderer<LevelListItem>> {
return listOf(
MyLeafFolderRenderer(),
MyParentFoldermRenderer()
)
}
}
```
Example of implementation of a leaf specific Renderer (as you can see it is pretty simple):
```kotlin
open class MyLeafFolderRenderer: LevelListItemRenderer() {
override fun bind() {
rootView.foldernameTV.text = content.levelListItemName
// ... assign other properties from item to rootView elements
}
override fun inflate(inflater: LayoutInflater?, parent: ViewGroup?): View {
return inflater!!.inflate(R.layout.my_leaf_item_row_layout, parent, false) // this layout disallows children from UI, for example, does not contain button for toggling children
}
}
```
Example of implementation of the no-leaf or parent specific Renderer
```kotlin
class MyParentFolderRenderer : MyLeafFolderRenderer() {
override fun hookListeners(rootView: View) {
super.hookListeners(rootView)
rootView.showchildrenclicker.setOnClickListener {
view ->
view.isClickable = false
toggleChildren(view, content)
}
}
override fun bind() {
super.bind()
differentiateUnfoldedCondition()
}
override fun inflate(inflater: LayoutInflater?, parent: ViewGroup?): View {
return inflater!!.inflate(R.layout.my_parent_item_row_layout, parent, false)
}
override fun animateToggleChildren(areChildrenUnfolded: Boolean) {
rotateArrow(areChildrenUnfolded)
}
override fun toggleChildrenFinished() {
super.toggleChildrenFinished()
rootView.showchildrenclicker.isClickable = true
}
private fun differentiateUnfoldedCondition() {
rootView.showchildrenbutton.rotation =
if(!item.areChildrenUnfolded) 0f
else 90f
}
private fun rotateArrow(areChildrenUnfolded: Boolean) {
rootView.showchildrenbutton.animate().rotation(
if(areChildrenUnfolded) 0f
else 90f
).setDuration(200)
.withEndAction {
toggleChildrenFinished()
}
}
}
```
**Fourth**, create your level list Adapter and set it to your RecyclerView.
You can use the default implementation of `LevelListBuilder`, `LevelListBuilderImpl`.
Example:
```kotlin
adapter = FolderRVRendererAdapter(
this,
LevelListBuilderImpl("/", 10),
16f,
FolderRendererBuilder(listOf(MyLeafFolderRenderer(), MyParentFolderRenderer()))
)
recyclerView.adapter = adapter.adapter
adapter.submitPlainList(plainFolderList)
```
**Fifth**, implement listener method. Example:
```kotlin
override fun onItemClicked(item: LevelListItem) {
val folder = item.item
Toast.makeText(context, "Item ${folder.itemName} was clicked!", Toast.LENGTH_SHORT).show()
}
```
And that's it!
\ No newline at end of file
plugins {
id(Plugins.androidApplication)
kotlin(Plugins.android)
kotlin(Plugins.androidExtensions)
id(Plugins.remalDependencyUpdates) version(Versions.remalDependencyUpdates)
}
android {
compileSdkVersion (Apps.compileSdk)
buildToolsVersion (Apps.buildToolsVersion)
defaultConfig {
applicationId = Apps.appId
minSdkVersion (Apps.minSdk)
targetSdkVersion (Apps.targetSdk)
versionCode = Apps.versionCode
versionName = Apps.versionName
testInstrumentationRunner = Apps.testInstrumentationRunner
vectorDrawables.useSupportLibrary = true
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
testOptions {
animationsDisabled = true
unitTests(delegateClosureOf<com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions>{
isIncludeAndroidResources = true
})
}
}
dependencies {
implementation (fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation (project(":${Projects.animatedLevelList}"))
implementation (Libs.kotlin)
implementation (Libs.appCompat)
implementation (Libs.androidxCore)
implementation (Libs.constraintLayout)
implementation (Libs.material)
implementation (Libs.recyclerView)
implementation (Libs.renderers)
// unit test dependencies
testImplementation (TestLibs.junit)
testImplementation (TestLibs.robolectric)
testImplementation (TestLibs.Mockito.mockitoCore)
testImplementation (TestLibs.Mockito.mockitoInLine)
testImplementation (TestLibs.Mockito.mockitoKotlin)
testImplementation (TestLibs.AndroidxTest.androidxTestCoreKtx)
testImplementation (TestLibs.AndroidxTest.testExtJunitKtx)
testImplementation (TestLibs.AndroidxTest.androidxTestRules)
// android test dependencies
androidTestImplementation (TestLibs.junit)
androidTestImplementation (TestLibs.Espresso.espressoCore)
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="security.pEp.experiments">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".levellistexperiment.defaultsample.DefaultLevelListActivity"/>
<activity android:name=".levellistexperiment.justnamefolder1displayersample.OneDisplayerListActivity" />
<activity android:name=".levellistexperiment.justnamefolder2displayerssample.MyItemListActivity" />
<activity android:name=".mainscreen.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package security.pEp.experiments.levellistexperiment.defaultsample
data class Account(
var inboxFolderName: String = INBOX,
var draftsFolderName: String = DRAFTS,
var sentFolderName: String = SENT,
var trashFolderName: String = TRASH,
var archiveFolderName: String = ARCHIVE,
var spamFolderName: String = SPAM
) {
private val outboxFolderName: String
get() = OUTBOX
companion object {
const val OUTBOX = "PEP_INTERNAL_OUTBOX"
const val INBOX = "INBOX"
const val DRAFTS = "DRAFTS"
const val SENT = "SENT"
const val TRASH = "TRASH"
const val ARCHIVE = "ARCHIVE"
const val SPAM = "SPAM"
}
fun getAccountFolderNames(): List<String> {
return listOf(
inboxFolderName,
draftsFolderName,
sentFolderName,
trashFolderName,
archiveFolderName,
spamFolderName,
outboxFolderName
)
}
}
package security.pEp.experiments.levellistexperiment.defaultsample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import security.pEp.experiments.R
class DefaultLevelListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.foler_list_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(
R.id.container,
DefaultLevelListFragment.newInstance()
)
.commitNow()
}
}
}
package security.pEp.experiments.levellistexperiment.defaultsample
import android.R.layout.simple_spinner_item
import android.R.layout.simple_spinner_dropdown_item</