Repository: bitvale/DroidMotion
Branch: master
Commit: 8bf3d8e269fd
Files: 73
Total size: 100.9 KB
Directory structure:
gitextract_vvxwq36_/
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── bitvale/
│ │ └── droidmotion/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── bitvale/
│ │ │ └── droidmotion/
│ │ │ ├── MainActivity.kt
│ │ │ ├── adapter/
│ │ │ │ ├── BaseViewHolder.kt
│ │ │ │ └── RecyclerAdapter.kt
│ │ │ ├── common/
│ │ │ │ ├── Constants.kt
│ │ │ │ └── Extensions.kt
│ │ │ ├── fragment/
│ │ │ │ ├── BaseFragment.kt
│ │ │ │ ├── DetailsFragment.kt
│ │ │ │ └── RecyclerFragment.kt
│ │ │ ├── listener/
│ │ │ │ ├── BottomNavigationViewListener.kt
│ │ │ │ └── OnBackPressedListener.kt
│ │ │ └── model/
│ │ │ └── DataProvider.kt
│ │ └── res/
│ │ ├── anim/
│ │ │ ├── item_animation_fall_down.xml
│ │ │ └── layout_animation_fall_down.xml
│ │ ├── animator/
│ │ │ └── main_list_animator.xml
│ │ ├── drawable/
│ │ │ ├── bottom_navigation_selector.xml
│ │ │ ├── ic_complaint.xml
│ │ │ ├── ic_correct.xml
│ │ │ ├── ic_error.xml
│ │ │ ├── ic_export.xml
│ │ │ ├── ic_file.xml
│ │ │ ├── ic_incorrect.xml
│ │ │ ├── ic_left.xml
│ │ │ ├── ic_menu_account.xml
│ │ │ ├── ic_menu_dialog.xml
│ │ │ ├── ic_menu_gift.xml
│ │ │ ├── ic_menu_main.xml
│ │ │ ├── ic_menu_wallet.xml
│ │ │ ├── ic_negative.xml
│ │ │ ├── ic_not_received.xml
│ │ │ ├── ic_positive.xml
│ │ │ ├── ic_received.xml
│ │ │ ├── ic_right.xml
│ │ │ ├── ic_sort.xml
│ │ │ ├── img_apple.xml
│ │ │ ├── img_dribble.xml
│ │ │ ├── img_facebook.xml
│ │ │ ├── img_github.xml
│ │ │ ├── img_google_play.xml
│ │ │ ├── img_instagram.xml
│ │ │ ├── img_twitter.xml
│ │ │ ├── img_vimeo.xml
│ │ │ ├── img_vk.xml
│ │ │ ├── img_youtube.xml
│ │ │ └── splash_screen.xml
│ │ ├── layout/
│ │ │ ├── activity_main.xml
│ │ │ ├── fragment_details.xml
│ │ │ ├── fragment_recycler.xml
│ │ │ ├── item_card.xml
│ │ │ └── item_details.xml
│ │ ├── menu/
│ │ │ └── bottom_navigation_main.xml
│ │ ├── transition/
│ │ │ └── shared_element_transition.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── values-sw600dp/
│ │ │ └── dimens.xml
│ │ └── values-sw720dp/
│ │ └── dimens.xml
│ └── test/
│ └── java/
│ └── com/
│ └── bitvale/
│ └── droidmotion/
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches/build_file_checksums.ser
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild
.idea
app/release/
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2018 Alexander Kolpakov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# DroidMotion
[](http://developer.android.com/index.html)
[](https://android-arsenal.com/api?level=19)
[](/LICENSE)
This is an Android sample project that shows how to implement a simple android motion.
#
You can see the description of the implementation on medium and get the sample app with similar motions on Google Play.
#
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
applicationId "com.bitvale.droidmotion"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
useProguard false
minifyEnabled false
}
release {
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
androidExtensions {
experimental = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Testing dependencies
testImplementation "junit:junit:$rootProject.junitVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$rootProject.kotlinVersion"
implementation "androidx.appcompat:appcompat:$rootProject.androidxVersion"
implementation "androidx.recyclerview:recyclerview:$rootProject.androidxVersion"
implementation "androidx.cardview:cardview:$rootProject.androidxVersion"
implementation "com.google.android.material:material:$rootProject.materialVersion"
implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion"
implementation "androidx.core:core-ktx:$rootProject.androidxVersion"
}
================================================
FILE: app/proguard-rules.pro
================================================
# 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
# hideBottomNavigationView the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/bitvale/droidmotion/ExampleInstrumentedTest.kt
================================================
package com.bitvale.droidmotion
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.bitvale.animation", appContext.packageName)
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/MainActivity.kt
================================================
package com.bitvale.droidmotion
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.bitvale.androidmotion.R
import com.bitvale.droidmotion.common.replaceFragmentInActivity
import com.bitvale.droidmotion.fragment.RecyclerFragment
import com.bitvale.droidmotion.listener.BottomNavigationViewListener
import com.bitvale.droidmotion.listener.OnBackPressedListener
import kotlinx.android.synthetic.main.activity_main.*
/**
* Created by Alexander Kolpakov on 25.07.2018
*/
class MainActivity : AppCompatActivity(), BottomNavigationViewListener {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupFragment(RecyclerFragment.newInstance())
}
private fun setupFragment(fragment: Fragment) {
supportFragmentManager.findFragmentById(R.id.fragment_container)
?: fragment.let {
replaceFragmentInActivity(it, R.id.fragment_container)
}
}
override fun hideBottomNavigationView() {
if (bottom_navigation.translationY == 0f)
bottom_navigation.animate()
.translationY(bottom_navigation.height.toFloat())
.setDuration(250)
.start()
}
override fun showBottomNavigationView() {
if (bottom_navigation.translationY >= bottom_navigation.height.toFloat())
bottom_navigation.animate()
.translationY(0f)
.setDuration(400)
.start()
}
override fun onBackPressed() {
val fragmentList = supportFragmentManager.fragments
var proceedToSuper = true
for (fragment in fragmentList) {
if (fragment is OnBackPressedListener) {
proceedToSuper = false
(fragment as OnBackPressedListener).onBackPressed()
}
}
if (proceedToSuper) super.onBackPressed()
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/adapter/BaseViewHolder.kt
================================================
package com.bitvale.droidmotion.adapter
import android.os.Build
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import com.bitvale.androidmotion.R
import com.bitvale.droidmotion.common.inflate
import com.bitvale.droidmotion.model.DataProvider.BaseData
import com.bitvale.droidmotion.model.DataProvider.Card
import com.bitvale.droidmotion.model.DataProvider.Details
import com.bitvale.droidmotion.common.TRANSITION_CARD
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_card.*
import kotlinx.android.synthetic.main.item_details.*
/**
* Created by Alexander Kolpakov on 29.07.2018
*/
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer {
override val containerView: View?
get() = itemView
abstract fun bind(data: T, listener: View.OnClickListener? = null)
class CardViewHolder(parent: ViewGroup) : BaseViewHolder(parent.inflate(R.layout.item_card)) {
override fun bind(data: Card, listener: View.OnClickListener?) {
containerView?.setOnClickListener(listener)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
containerView?.transitionName = TRANSITION_CARD + adapterPosition
}
tv_title.text = data.name
tv_amount.text = data.amount
tv_date.text = data.date
tv_status.text = data.status.code
img_status.setImageResource(data.status.iconId)
img_card.setImageResource(data.imageId)
}
}
class DetailsViewHolder(parent: ViewGroup) : BaseViewHolder(parent.inflate(R.layout.item_details)) {
override fun bind(data: Details, listener: View.OnClickListener?) {
tv_details_title.text = data.title
tv_details_subtitle.text = data.subtitle
tv_details_amount.text = data.amount
}
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/adapter/RecyclerAdapter.kt
================================================
package com.bitvale.droidmotion.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import com.bitvale.droidmotion.model.DataProvider.BaseData
import com.bitvale.droidmotion.model.DataProvider.Details
import com.bitvale.droidmotion.adapter.BaseViewHolder.DetailsViewHolder
import com.bitvale.droidmotion.adapter.BaseViewHolder.CardViewHolder
/**
* Created by Alexander Kolpakov on 17.07.2018
*/
class RecyclerAdapter(private var dataSet: List, private var listener: View.OnClickListener? = null)
: RecyclerView.Adapter>() {
@Suppress("UNCHECKED_CAST")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return when (viewType) {
0 -> DetailsViewHolder(parent) as BaseViewHolder
else -> CardViewHolder(parent) as BaseViewHolder
}
}
override fun getItemCount() = dataSet.size
override fun getItemViewType(position: Int): Int {
val type = dataSet[0].javaClass
return when (type) {
Details::class.java -> 0
else -> 1
}
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) = holder.bind(dataSet[position], listener)
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/common/Constants.kt
================================================
package com.bitvale.droidmotion.common
const val TRANSITION_CARD = "card_transition_"
const val TRANSITION_TOOLBAR = "toolbar_transition"
const val EXTRA_COORDINATES = "coordinates"
const val EXTRA_POSITION = "position"
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/common/Extensions.kt
================================================
package com.bitvale.droidmotion.common
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.os.Build
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.view.drawToBitmap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import com.bitvale.androidmotion.R
/**
* Created by Alexander Kolpakov on 17.07.2018
*/
fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
}
fun Context.getStatusBarHeight(): Int {
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
return if (resourceId > 0) resources.getDimensionPixelSize(resourceId)
else 0
}
fun Context.getToolbarHeight(): Int {
val tv = TypedValue()
return if (theme.resolveAttribute(android.R.attr.actionBarSize, tv, true))
TypedValue.complexToDimensionPixelSize(tv.data, resources.displayMetrics)
else 0
}
fun AppCompatActivity.replaceFragmentInActivity(fragment: Fragment, frameId: Int) {
supportFragmentManager.transact {
replace(frameId, fragment)
}
}
private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Unit) {
beginTransaction().apply {
action()
}.commit()
}
fun View.copyViewImage(): View {
val copy = ImageView(context)
val bitmap = drawToBitmap()
copy.setImageBitmap(bitmap)
// On pre-Lollipop when we create a copy, the card view's shadow is copied too as content and
// we do not need an additional card view.
return (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CardView(context).apply {
cardElevation = resources.getDimension(R.dimen.card_elevation)
radius = resources.getDimension(R.dimen.card_corner_radius)
addView(copy)
}
} else {
copy
}).apply {
layoutParams = this@copyViewImage.layoutParams
layoutParams.height = this@copyViewImage.height
layoutParams.width = this@copyViewImage.width
x = this@copyViewImage.x
y = this@copyViewImage.y
}
}
fun Animator.withEndAction(action: () -> Unit): Animator {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
action()
}
})
return this
}
fun Animator.withStartAction(action: () -> Unit): Animator {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
action()
}
})
return this
}
inline fun supportsLollipop(action: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
action()
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/fragment/BaseFragment.kt
================================================
package com.bitvale.droidmotion.fragment
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.cardview.widget.CardView
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import com.bitvale.androidmotion.R
import com.bitvale.droidmotion.common.getStatusBarHeight
/**
* Created by Alexander Kolpakov on 26.07.2018
*/
open class BaseFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupToolbarMargin(view)
}
private fun setupToolbarMargin(view: View) {
val toolbar = view.findViewById(R.id.toolbar)
toolbar?.let {
val statusBarHeight = context?.getStatusBarHeight()
val lp = it.layoutParams as ViewGroup.LayoutParams
lp.height += statusBarHeight as Int
if (it is CardView) {
it.setContentPadding(0, statusBarHeight, 0, 0)
} else {
it.updatePadding(top = statusBarHeight)
}
}
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/fragment/DetailsFragment.kt
================================================
package com.bitvale.droidmotion.fragment
import android.animation.AnimatorInflater
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnticipateInterpolator
import android.view.animation.OvershootInterpolator
import androidx.core.view.doOnLayout
import androidx.core.view.updatePadding
import com.bitvale.androidmotion.R
import com.bitvale.droidmotion.common.*
import com.bitvale.droidmotion.listener.OnBackPressedListener
import com.bitvale.droidmotion.model.DataProvider
import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.android.synthetic.main.item_card.*
/**
* Created by Alexander Kolpakov on 25.07.2018
*/
class DetailsFragment : BaseFragment(), OnBackPressedListener {
private lateinit var coordinates: FloatArray
companion object {
const val TAG = "DetailsFragment"
fun newInstance(coordinates: FloatArray, adapterPosition: Int): DetailsFragment {
val bundle = Bundle().apply {
putFloatArray(EXTRA_COORDINATES, coordinates)
putInt(EXTRA_POSITION, adapterPosition)
}
return DetailsFragment().apply { arguments = bundle }
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
coordinates = it.getFloatArray(EXTRA_COORDINATES)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_details, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val position = if (arguments != null) (arguments as Bundle).getInt(EXTRA_POSITION)
else 0
setupViews(position)
if (savedInstanceState == null) {
animateToolbar()
fab_negative.animate()
.translationY(0f)
.setDuration(650)
.setInterpolator(OvershootInterpolator(4f))
.start()
fab_positive.animate()
.translationY(0f)
.setStartDelay(100)
.setDuration(650)
.setInterpolator(OvershootInterpolator(4f))
.start()
} else {
toolbar.alpha = 1f
}
}
private fun setupViews(position: Int) {
supportsLollipop {
details_card.transitionName = TRANSITION_CARD + position
toolbar_container.transitionName = TRANSITION_TOOLBAR
}
(details_card.layoutParams as ViewGroup.MarginLayoutParams).topMargin = coordinates[2].toInt()
val data = DataProvider.getCardData()[position]
tv_title.text = data.name
tv_amount.text = data.amount
tv_date.text = data.date
tv_status.text = data.status.code
img_status.setImageResource(data.status.iconId)
img_card.setImageResource(data.imageId)
fab_negative.setOnClickListener { onBackPressed() }
with(recycler_view) {
adapter = com.bitvale.droidmotion.adapter.RecyclerAdapter(DataProvider.getDetailsData())
setHasFixedSize(true)
fab_negative.doOnLayout {
val paddingBottom = (paddingBottom + fab_negative.height * 1.5).toInt()
updatePadding(bottom = paddingBottom)
}
}
}
override fun onBackPressed() {
animateViewsOut()
}
private fun animateViewsOut() {
val translateTo = fab_negative.height * 2f
AnimatorInflater.loadAnimator(activity, R.animator.main_list_animator).apply {
setTarget(recycler_view)
start()
}
fab_negative.animate()
.translationY(translateTo)
.setDuration(350)
.setInterpolator(AnticipateInterpolator(2f))
.start()
fab_positive.animate()
.translationY(translateTo)
.setStartDelay(50)
.setDuration(350)
.withEndAction {
activity?.supportFragmentManager?.popBackStack()
}
.setInterpolator(AnticipateInterpolator(2f))
.start()
animateToolbar(0f, 350)
}
private fun animateToolbar(alphaTo: Float = 1f, duration: Long = 200) {
toolbar.animate().alpha(alphaTo).setDuration(duration).start()
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/fragment/RecyclerFragment.kt
================================================
package com.bitvale.droidmotion.fragment
import android.animation.AnimatorInflater
import android.animation.ValueAnimator
import android.os.Bundle
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnLayout
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bitvale.androidmotion.R
import com.bitvale.droidmotion.adapter.RecyclerAdapter
import com.bitvale.droidmotion.common.*
import com.bitvale.droidmotion.listener.BottomNavigationViewListener
import com.bitvale.droidmotion.model.DataProvider
import kotlinx.android.synthetic.main.fragment_recycler.*
/**
* Created by Alexander Kolpakov on 25.07.2018
*/
class RecyclerFragment : BaseFragment(), View.OnClickListener {
companion object {
const val TAG = "RecyclerFragment"
fun newInstance(): RecyclerFragment {
return RecyclerFragment()
}
}
var bottomNavListener: BottomNavigationViewListener? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_recycler, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
if (savedInstanceState == null) {
root.doOnLayout {
toolbar.animate()
.translationY(0f)
.alpha(1f)
.setDuration(400)
.start()
bottomNavListener?.showBottomNavigationView()
}
} else {
toolbar.alpha = 1f
toolbar.translationY = 0f
}
}
private fun setupViews() {
supportsLollipop {
details_toolbar_transition_helper.transitionName = TRANSITION_TOOLBAR
}
details_toolbar_transition_helper.translationY = -resources.getDimension(R.dimen.details_toolbar_container_height)
toolbar.translationY = -toolbar.context.getToolbarHeight().toFloat()
val elevation = resources.getDimension(R.dimen.toolbar_elevation)
with(recycler_view) {
adapter = RecyclerAdapter(DataProvider.getCardData(), this@RecyclerFragment)
setHasFixedSize(true)
val lm = layoutManager as LinearLayoutManager
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) bottomNavListener?.hideBottomNavigationView()
if (dy < 0) bottomNavListener?.showBottomNavigationView()
if (lm.findFirstCompletelyVisibleItemPosition() == 0) {
if (toolbar.cardElevation == 0f) return
animateToolbarElevation(true)
} else {
if (toolbar.cardElevation > 0f) return
toolbar.cardElevation = elevation
}
}
})
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (activity is BottomNavigationViewListener) {
bottomNavListener = activity as BottomNavigationViewListener
} else {
throw ClassCastException("$activity must implement BottomNavigationViewListener")
}
}
override fun onClick(view: View) {
val fragmentTransaction = initFragmentTransaction(view)
val copy = view.copyViewImage()
copy.y += toolbar.height
root.addView(copy)
view.visibility = View.INVISIBLE
startAnimation(copy, fragmentTransaction)
}
private fun initFragmentTransaction(view: View): FragmentTransaction? {
val toY = view.resources.getDimensionPixelOffset(R.dimen.details_toolbar_container_height) - view.height / 2f
val positions = FloatArray(3)
positions[0] = view.x
positions[1] = view.y + toolbar.height
positions[2] = toY
val adapterPosition = recycler_view.getChildAdapterPosition(view)
val detailsFragment = DetailsFragment.newInstance(positions, adapterPosition)
val transaction = fragmentManager?.beginTransaction()
?.replace(R.id.fragment_container, detailsFragment, DetailsFragment.TAG)
?.addToBackStack(null)
supportsLollipop {
val transition = TransitionInflater.from(context)
.inflateTransition(R.transition.shared_element_transition)
detailsFragment.sharedElementEnterTransition = transition
transaction
?.addSharedElement(view, view.transitionName)
?.addSharedElement(details_toolbar_transition_helper, details_toolbar_transition_helper.transitionName)
}
return transaction
}
private fun startAnimation(view: View, fragmentTransaction: FragmentTransaction?) {
AnimatorInflater.loadAnimator(activity, R.animator.main_list_animator).apply {
setTarget(recycler_view)
withStartAction { if (toolbar.cardElevation > 0) animateToolbarElevation(true) }
withEndAction {
recycler_view.visibility = View.INVISIBLE
val toY = view.resources.getDimensionPixelOffset(R.dimen.details_toolbar_container_height) - view.height / 2f
view.animate().y(toY).start()
toolbar.animate()
.translationY(-toolbar.height.toFloat())
.alpha(0f)
.setDuration(600)
.withStartAction {
bottomNavListener?.hideBottomNavigationView()
details_toolbar_transition_helper.animate().translationY(0f).setDuration(500).start()
}
.withEndAction {
fragmentTransaction?.commitAllowingStateLoss()
}
.start()
}
start()
}
}
private fun animateToolbarElevation(animateOut: Boolean) {
var valueFrom = resources.getDimension(R.dimen.toolbar_elevation)
var valueTo = 0f
if (!animateOut) {
valueTo = valueFrom
valueFrom = 0f
}
ValueAnimator.ofFloat(valueFrom, valueTo).setDuration(250).apply {
startDelay = 0
addUpdateListener { toolbar.cardElevation = it.animatedValue as Float }
start()
}
}
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/listener/BottomNavigationViewListener.kt
================================================
package com.bitvale.droidmotion.listener
/**
* Created by Alexander Kolpakov on 29.07.2018
*/
interface BottomNavigationViewListener {
fun hideBottomNavigationView()
fun showBottomNavigationView()
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/listener/OnBackPressedListener.kt
================================================
package com.bitvale.droidmotion.listener
/**
* Created by Alexander Kolpakov on 26.07.2018
*/
interface OnBackPressedListener {
fun onBackPressed()
}
================================================
FILE: app/src/main/java/com/bitvale/droidmotion/model/DataProvider.kt
================================================
package com.bitvale.droidmotion.model
import androidx.annotation.DrawableRes
import com.bitvale.androidmotion.R
/**
* Created by Alexander Kolpakov on 24.07.2018
*/
object DataProvider {
fun getCardData(): List {
val data = ArrayList()
data.add(Card(0, "Google Play", "28.07.2018", "$38,456.78", Status.COMPLAINT, R.drawable.img_google_play))
data.add(Card(1, "Twitter", "27.07.2018", "$1,550.60", Status.RECEIVED, R.drawable.img_twitter))
data.add(Card(2, "YouTube", "27.07.2018", "$14,340.00", Status.CORRECT, R.drawable.img_youtube))
data.add(Card(3, "Dribbble", "26.07.2018", "$2,678.27", Status.ERROR, R.drawable.img_dribble))
data.add(Card(4, "Apple Store", "26.07.2018", "$20,479.12", Status.CORRECT, R.drawable.img_apple))
data.add(Card(5, "VK", "25.07.2018", "$13,846.13", Status.INCORRECT, R.drawable.img_vk))
data.add(Card(6, "Instagram", "25.07.2018", "$24,856.17", Status.NOT_RECEIVED, R.drawable.img_instagram))
data.add(Card(7, "Github", "24.07.2018", "$376.90", Status.COMPLAINT, R.drawable.img_github))
data.add(Card(8, "Vimeo", "23.07.2018", "$7,568.02", Status.RECEIVED, R.drawable.img_vimeo))
data.add(Card(9, "Facebook", "10.07.2018", "$18,347.32", Status.INCORRECT, R.drawable.img_facebook))
return data
}
fun getDetailsData(): List {
val data = ArrayList()
data.add(Details(0, "In App purchase\"Gem\"", "$14,340.00 X1 (including VAT 10%)", "$14,340.00"))
data.add(Details(0, "In App purchase \"Money\"", "$2,456.78 X1 (including VAT 10%)", "$2,456.78"))
data.add(Details(0, "Interstitial Ads", "$1,150.15 X1 (including VAT 10%)", "$1,150.15"))
data.add(Details(0, "Rewarded video", "$566.20 X1 (including VAT 10%)", "$566.20"))
return data
}
interface BaseData
data class Card(val id: Int, val name: String, val date: String, val amount: String, val status: Status,
@DrawableRes val imageId: Int) : BaseData
data class Details(val id: Int, val title: String, val subtitle: String, val amount: String) : BaseData
enum class Status(val code: String, @DrawableRes val iconId: Int) {
CORRECT("Correct", R.drawable.ic_correct),
INCORRECT("Incorrect", R.drawable.ic_incorrect),
COMPLAINT("Complaint", R.drawable.ic_complaint),
ERROR("Error", R.drawable.ic_error),
RECEIVED("Received", R.drawable.ic_received),
NOT_RECEIVED("Not received", R.drawable.ic_not_received)
}
}
================================================
FILE: app/src/main/res/anim/item_animation_fall_down.xml
================================================
================================================
FILE: app/src/main/res/anim/layout_animation_fall_down.xml
================================================
================================================
FILE: app/src/main/res/animator/main_list_animator.xml
================================================
================================================
FILE: app/src/main/res/drawable/bottom_navigation_selector.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_complaint.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_correct.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_error.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_export.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_file.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_incorrect.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_left.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_menu_account.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_menu_dialog.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_menu_gift.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_menu_main.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_menu_wallet.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_negative.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_not_received.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_positive.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_received.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_right.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_sort.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_apple.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_dribble.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_facebook.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_github.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_google_play.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_instagram.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_twitter.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_vimeo.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_vk.xml
================================================
================================================
FILE: app/src/main/res/drawable/img_youtube.xml
================================================
================================================
FILE: app/src/main/res/drawable/splash_screen.xml
================================================
-
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_details.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_recycler.xml
================================================
================================================
FILE: app/src/main/res/layout/item_card.xml
================================================
================================================
FILE: app/src/main/res/layout/item_details.xml
================================================
================================================
FILE: app/src/main/res/menu/bottom_navigation_main.xml
================================================
================================================
FILE: app/src/main/res/transition/shared_element_transition.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3aa3f7
#025497
#008dff
#ff4d6a
@color/colorAccent
@color/colorAccent
#505a595b
#4d4d4d
#eee
@android:color/white
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
26sp
22sp
18sp
16sp
14sp
12sp
4dp
8dp
16dp
24dp
32dp
48dp
64dp
@dimen/space_tiny
@dimen/space_small
@dimen/space_normal
@dimen/space_large
@dimen/space_medium
@dimen/space_tiny
@dimen/space_small
@dimen/space_normal
@dimen/space_medium
@dimen/space_large
@dimen/space_xlarge
100dp
@dimen/space_tiny
@dimen/space_tiny
@dimen/space_tiny
@dimen/space_small
56dp
@dimen/space_medium
156dp
146dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
DroidMotion
My income
Status
Date
Amount
Back
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/main/res/values-sw600dp/dimens.xml
================================================
164dp
================================================
FILE: app/src/main/res/values-sw720dp/dimens.xml
================================================
8dp
12dp
20dp
36dp
52dp
68dp
28sp
24sp
20sp
18sp
16sp
14sp
76dp
196dp
================================================
FILE: app/src/test/java/com/bitvale/droidmotion/ExampleUnitTest.kt
================================================
package com.bitvale.droidmotion
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
// Sdk and tools
compileSdkVersion = 28
minSdkVersion = 19
targetSdkVersion = 28
// App dependencies
constraintLayoutVersion = '1.1.2'
gradleVersion = '3.3.0-alpha10'
junitVersion = '4.12'
kotlinVersion = '1.2.61'
androidxVersion = "1.0.0-rc02"
materialVersion = "1.0.0-rc01"
constraintLayoutVersion = '1.1.2'
}
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$gradleVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Sep 12 22:37:27 EET 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# 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
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# 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
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app'