├── .gitignore
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── hearsilent
│ │ └── amazingavatar
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── hearsilent
│ │ │ └── amazingavatar
│ │ │ ├── MainActivity.kt
│ │ │ ├── adapter
│ │ │ └── DemoViewHolder.kt
│ │ │ ├── callbacks
│ │ │ └── AvatarCallback.kt
│ │ │ ├── libs
│ │ │ ├── AppBarStateChangeListener.kt
│ │ │ ├── FlingBehavior.kt
│ │ │ ├── NetworkHelper.kt
│ │ │ └── Utils.kt
│ │ │ └── models
│ │ │ └── AvatarModel.kt
│ └── res
│ │ ├── drawable
│ │ └── avatar_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── item_demo.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── scene_header.xml
│ └── test
│ └── java
│ └── hearsilent
│ └── amazingavatar
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── device-2018-06-12-232525_framed.png
├── device-2018-06-12-232532_framed.png
└── screenrecord.gif
├── settings.gradle
└── web_hi_res_512.png
/.gitignore:
--------------------------------------------------------------------------------
1 | #built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 | release/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Windows thumbnail db
28 | Thumbs.db
29 |
30 | # OSX files
31 | .DS_Store
32 |
33 | # Eclipse project files
34 | .classpath
35 | .project
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio Navigation editor temp files
41 | .navigation/
42 |
43 | # Android Studio captures folder
44 | captures/
45 |
46 | # IntelliJ
47 | *.iml
48 | */.idea/*
49 | /.idea/*
50 | !/.idea/codeStyles/
51 |
52 | # Keystore files
53 | # Uncomment the following line if you do not want to check your keystore files in.
54 | #*.jks
55 |
56 | # External native build folder generated in Android Studio 2.2 and later
57 | .externalNativeBuild
58 |
59 | # Google Services (e.g. APIs or Firebase)
60 | #google-services.json
61 |
62 | # Freeline
63 | freeline.py
64 | freeline/
65 | freeline_project_description.json
66 |
67 | # fastlane
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots
71 | fastlane/test_output
72 | fastlane/readme.md
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | xmlns:android
91 |
92 | ^$
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | xmlns:.*
102 |
103 | ^$
104 |
105 |
106 | BY_NAME
107 |
108 |
109 |
110 |
111 |
112 |
113 | .*:id
114 |
115 | http://schemas.android.com/apk/res/android
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | .*:name
125 |
126 | http://schemas.android.com/apk/res/android
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | name
136 |
137 | ^$
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | style
147 |
148 | ^$
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | .*
158 |
159 | ^$
160 |
161 |
162 | BY_NAME
163 |
164 |
165 |
166 |
167 |
168 |
169 | .*
170 |
171 | http://schemas.android.com/apk/res/android
172 |
173 |
174 | ANDROID_ATTRIBUTE_ORDER
175 |
176 |
177 |
178 |
179 |
180 |
181 | .*
182 |
183 | .*
184 |
185 |
186 | BY_NAME
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 HearSilent
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AmazingAvatar
2 |
3 | [](https://github.com/hearsilent/AmazingAvatar)
4 | [](https://github.com/hearsilent/AmazingAvatar/blob/master/LICENSE)
5 |
6 | An android amazing avatar anim in CollapsingToolbarLayout with MotionLayout.
7 |
8 | ## Screenshot
9 |
10 |
11 |
12 | ### Expanded
13 |
14 |
15 |
16 | ### Collapsed
17 |
18 |
19 |
20 | ## Usage
21 |
22 | *For a working implementation, please have a look at the Sample Project - sample*
23 |
24 |
25 |
26 | Just Clone and Build.
27 |
28 | ## Customization
29 |
30 | You can change header view to what you want.
31 |
32 | ```xml
33 |
40 |
41 |
47 |
48 |
59 |
60 |
67 |
68 |
82 |
83 |
94 |
95 |
101 |
102 |
115 |
116 |
117 | ```
118 |
119 | ## Compatibility
120 |
121 | Android LOLLIPOP 5.0+
122 |
123 | ## Credits
124 |
125 | Avatars from [maximedegreve/TinyFaces](https://github.com/maximedegreve/TinyFaces).
126 |
127 | ## Let me know!
128 |
129 | I'd be really happy if you sent me links to your projects where you use my component. Just send an
130 | email to hear.silent1995+github@gmail.com And do let me know if you have any questions or suggestion
131 | regarding the example.
132 |
133 | ## License
134 |
135 | MIT License
136 |
137 | Copyright (c) 2023 HearSilent
138 |
139 | Permission is hereby granted, free of charge, to any person obtaining a copy
140 | of this software and associated documentation files (the "Software"), to deal
141 | in the Software without restriction, including without limitation the rights
142 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
143 | copies of the Software, and to permit persons to whom the Software is
144 | furnished to do so, subject to the following conditions:
145 |
146 | The above copyright notice and this permission notice shall be included in all
147 | copies or substantial portions of the Software.
148 |
149 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
150 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
151 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
152 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
153 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
154 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
155 | SOFTWARE.
156 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'org.jetbrains.kotlin.kapt'
5 | }
6 |
7 | android {
8 | compileSdk 33
9 | namespace 'hearsilent.amazingavatar'
10 | defaultConfig {
11 | applicationId namespace
12 | minSdk 21
13 | targetSdk 33
14 | versionCode 200
15 | versionName "2.0.0"
16 |
17 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
18 | }
19 |
20 | buildFeatures {
21 | viewBinding true
22 | }
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 |
41 | implementation 'androidx.core:core-ktx:1.10.0'
42 | implementation 'androidx.appcompat:appcompat:1.6.1'
43 | implementation 'com.google.android.material:material:1.8.0'
44 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
45 |
46 | implementation 'com.squareup.okhttp3:okhttp:4.10.0'
47 | implementation 'io.coil-kt:coil:2.2.2'
48 |
49 | testImplementation 'junit:junit:4.13.2'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
51 | }
52 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Program\AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/hearsilent/amazingavatar/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar;
2 |
3 | import android.content.Context;
4 | import androidx.test.platform.app.InstrumentationRegistry;
5 | import androidx.test.ext.junit.runners.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest {
18 |
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("hearsilent.amazingavatar", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar
2 |
3 | import android.os.Bundle
4 | import android.util.TypedValue
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.recyclerview.widget.DefaultItemAnimator
7 | import androidx.recyclerview.widget.DividerItemDecoration
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import coil.load
10 | import com.google.android.material.appbar.AppBarLayout
11 | import hearsilent.amazingavatar.adapter.DemoAdapter
12 | import hearsilent.amazingavatar.callbacks.AvatarCallback
13 | import hearsilent.amazingavatar.databinding.ActivityMainBinding
14 | import hearsilent.amazingavatar.libs.AppBarStateChangeListener
15 | import hearsilent.amazingavatar.libs.NetworkHelper
16 | import hearsilent.amazingavatar.libs.Utils
17 | import hearsilent.amazingavatar.models.AvatarModel
18 | import java.util.*
19 |
20 |
21 | class MainActivity : AppCompatActivity() {
22 |
23 | private lateinit var mBinding: ActivityMainBinding
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | mBinding = ActivityMainBinding.inflate(layoutInflater)
28 | setContentView(mBinding.root)
29 |
30 | setUpViews()
31 | fetchAvatar()
32 | }
33 |
34 | private fun setUpViews() {
35 | setUpToolbar()
36 | setUpRecyclerView()
37 | setUpAmazingAvatar()
38 | }
39 |
40 | private fun setUpRecyclerView() {
41 | mBinding.recyclerView.setHasFixedSize(true)
42 | mBinding.recyclerView.itemAnimator = DefaultItemAnimator()
43 | mBinding.recyclerView.addItemDecoration(
44 | DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
45 | )
46 | mBinding.recyclerView.layoutManager = LinearLayoutManager(this)
47 | mBinding.recyclerView.adapter = DemoAdapter()
48 | }
49 |
50 | private fun setUpToolbar() {
51 | mBinding.appBar.layoutParams.height = Utils.getDisplayMetrics(this).widthPixels * 9 / 16
52 | mBinding.appBar.requestLayout()
53 | setSupportActionBar(mBinding.toolbar)
54 | supportActionBar?.apply {
55 | setDisplayShowTitleEnabled(false)
56 | setDisplayHomeAsUpEnabled(true)
57 | }
58 | }
59 |
60 | private fun setUpAmazingAvatar() {
61 | val typedArray = obtainStyledAttributes(
62 | android.R.style.TextAppearance_Material_Widget_Toolbar_Title,
63 | intArrayOf(android.R.attr.textSize)
64 | )
65 | val collapsedTextSize = typedArray.getDimension(0, mBinding.textViewTitle.textSize)
66 | typedArray.recycle()
67 | val expandTextSize = mBinding.textViewTitleShadow.textSize
68 |
69 | mBinding.appBar.addOnOffsetChangedListener(object : AppBarStateChangeListener() {
70 | override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {}
71 |
72 | override fun onOffsetChanged(state: State, offset: Float) {
73 | mBinding.motionHeader.progress = offset
74 |
75 | val newTextSize = expandTextSize + (collapsedTextSize - expandTextSize) * offset
76 | mBinding.textViewTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSize)
77 | }
78 | })
79 | }
80 |
81 | /**
82 | * Avatar from TinyFaces ([TinyFaces](https://github.com/maximedegreve/TinyFaces))
83 | */
84 | private fun fetchAvatar() {
85 | NetworkHelper.getAvatar(object : AvatarCallback() {
86 | override fun onSuccess(avatarModel: AvatarModel) {
87 | super.onSuccess(avatarModel)
88 | if (isFinishing) {
89 | return
90 | }
91 | runOnUiThread {
92 | mBinding.imageViewAvatar.load(avatarModel.url) { crossfade(true) }
93 | val name = String.format(
94 | Locale.getDefault(), "%s %s", avatarModel.firstName, avatarModel.lastName
95 | )
96 | mBinding.textViewTitleShadow.text = name
97 | mBinding.textViewTitle.text = name
98 | }
99 | }
100 | })
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/adapter/DemoViewHolder.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.adapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import hearsilent.amazingavatar.R
8 | import hearsilent.amazingavatar.databinding.ItemDemoBinding
9 | import java.util.*
10 |
11 | class DemoAdapter : RecyclerView.Adapter() {
12 |
13 | class DemoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
14 | val binding = ItemDemoBinding.bind(view)
15 | }
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DemoViewHolder {
18 | return DemoViewHolder(
19 | LayoutInflater.from(parent.context).inflate(R.layout.item_demo, parent, false)
20 | )
21 | }
22 |
23 | override fun onBindViewHolder(holder: DemoViewHolder, position: Int) {
24 | holder.binding.textView.text =
25 | String.format(Locale.getDefault(), "HearSilent %d", position)
26 | }
27 |
28 | override fun getItemCount(): Int {
29 | return 100
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/callbacks/AvatarCallback.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.callbacks
2 |
3 | import hearsilent.amazingavatar.models.AvatarModel
4 |
5 | open class AvatarCallback {
6 | open fun onSuccess(avatarModel: AvatarModel) {}
7 | fun onFail() {}
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/libs/AppBarStateChangeListener.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.libs
2 |
3 | import com.google.android.material.appbar.AppBarLayout
4 | import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
5 | import kotlin.math.abs
6 |
7 | abstract class AppBarStateChangeListener : OnOffsetChangedListener {
8 |
9 | enum class State {
10 | EXPANDED, COLLAPSED, IDLE
11 | }
12 |
13 | private var currentState = State.IDLE
14 | var currentOffset = 0f
15 |
16 | override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
17 | if (i == 0) {
18 | if (currentState != State.EXPANDED) {
19 | onStateChanged(appBarLayout, State.EXPANDED)
20 | }
21 | currentState = State.EXPANDED
22 | } else if (abs(i) >= appBarLayout.totalScrollRange) {
23 | if (currentState != State.COLLAPSED) {
24 | onStateChanged(appBarLayout, State.COLLAPSED)
25 | }
26 | currentState = State.COLLAPSED
27 | } else {
28 | if (currentState != State.IDLE) {
29 | onStateChanged(appBarLayout, State.IDLE)
30 | }
31 | currentState = State.IDLE
32 | }
33 | val offset = abs(i / appBarLayout.totalScrollRange.toFloat())
34 | if (offset != currentOffset) {
35 | currentOffset = offset
36 | onOffsetChanged(currentState, offset)
37 | }
38 | }
39 |
40 | abstract fun onStateChanged(appBarLayout: AppBarLayout, state: State)
41 | abstract fun onOffsetChanged(state: State, offset: Float)
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/libs/FlingBehavior.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.libs
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.coordinatorlayout.widget.CoordinatorLayout
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.google.android.material.appbar.AppBarLayout
9 |
10 | class FlingBehavior : AppBarLayout.Behavior {
11 | private var isPositive = false
12 |
13 | constructor() {}
14 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
15 |
16 | override fun onNestedFling(
17 | coordinatorLayout: CoordinatorLayout,
18 | child: AppBarLayout, target: View, velocityX: Float,
19 | velocityY: Float, consumed: Boolean
20 | ): Boolean {
21 | var newVelocityY = velocityY
22 | var newConsumed = consumed
23 | if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
24 | newVelocityY *= -1
25 | }
26 | if (target is RecyclerView && velocityY < 0) {
27 | val firstChild = target.getChildAt(0)
28 | val childAdapterPosition = target.getChildAdapterPosition(firstChild)
29 | newConsumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD
30 | }
31 | return super.onNestedFling(
32 | coordinatorLayout,
33 | child,
34 | target,
35 | velocityX,
36 | newVelocityY,
37 | newConsumed
38 | )
39 | }
40 |
41 | override fun onNestedPreScroll(
42 | coordinatorLayout: CoordinatorLayout, child: AppBarLayout,
43 | target: View, dx: Int, dy: Int, consumed: IntArray, type: Int
44 | ) {
45 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
46 | isPositive = dy > 0
47 | }
48 |
49 | companion object {
50 | private const val TOP_CHILD_FLING_THRESHOLD = 3
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/libs/NetworkHelper.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.libs
2 |
3 | import hearsilent.amazingavatar.callbacks.AvatarCallback
4 | import hearsilent.amazingavatar.models.AvatarModel
5 | import okhttp3.*
6 | import org.json.JSONArray
7 | import java.io.IOException
8 | import java.util.concurrent.TimeUnit
9 |
10 | object NetworkHelper {
11 | private var mClient: OkHttpClient? = null
12 | private fun init() {
13 | mClient = OkHttpClient().newBuilder().followRedirects(false).followSslRedirects(false)
14 | .connectTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS)
15 | .readTimeout(30, TimeUnit.SECONDS).build()
16 | }
17 |
18 | private val client: OkHttpClient
19 | get() {
20 | mClient ?: init()
21 | return mClient!!
22 | }
23 |
24 | private const val AVATAR_URL = "https://tinyfac.es/api/data?limit=1&quality=0"
25 |
26 | fun getAvatar(callback: AvatarCallback) {
27 | val request: Request = Request.Builder().url(AVATAR_URL).get().build()
28 | client.newCall(request).enqueue(object : Callback {
29 | override fun onFailure(call: Call, e: IOException) {
30 | callback.onFail()
31 | }
32 |
33 | override fun onResponse(call: Call, response: Response) {
34 | if (response.code == 200) {
35 | try {
36 | val responseBodyCopy = response.peekBody(Long.MAX_VALUE)
37 | val body = responseBodyCopy.string()
38 | val jsonArray = JSONArray(body)
39 | val jsonObject = jsonArray.getJSONObject(0)
40 | val model = AvatarModel()
41 | model.url = jsonObject.getString("url")
42 | model.firstName = jsonObject.getString("first_name")
43 | model.lastName = jsonObject.getString("last_name")
44 | callback.onSuccess(model)
45 | } catch (ignore: Exception) {
46 | callback.onFail()
47 | }
48 | } else {
49 | callback.onFail()
50 | }
51 | }
52 | })
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/libs/Utils.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.libs
2 |
3 | import android.content.Context
4 | import android.util.DisplayMetrics
5 |
6 | object Utils {
7 |
8 | @JvmStatic
9 | fun getDisplayMetrics(context: Context): DisplayMetrics {
10 | val resources = context.resources
11 | return resources.displayMetrics
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/hearsilent/amazingavatar/models/AvatarModel.kt:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar.models
2 |
3 | class AvatarModel {
4 | @JvmField
5 | var url: String? = null
6 |
7 | @JvmField
8 | var firstName: String? = null
9 |
10 | @JvmField
11 | var lastName: String? = null
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/avatar_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
26 |
27 |
33 |
34 |
45 |
46 |
53 |
54 |
68 |
69 |
80 |
81 |
87 |
88 |
101 |
102 |
103 |
104 |
105 |
111 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AmazingAvatar
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/scene_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
33 |
34 |
41 |
42 |
43 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/test/java/hearsilent/amazingavatar/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package hearsilent.amazingavatar;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | }
6 |
7 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
8 | plugins {
9 | id 'com.android.application' version '7.4.2' apply false
10 | id 'com.android.library' version '7.4.2' apply false
11 | id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
12 | }
13 |
14 | task clean(type: Delete) {
15 | delete rootProject.buildDir
16 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.enableJetifier=true
13 | android.useAndroidX=true
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 28 17:24:19 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/device-2018-06-12-232525_framed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/screenshots/device-2018-06-12-232525_framed.png
--------------------------------------------------------------------------------
/screenshots/device-2018-06-12-232532_framed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/screenshots/device-2018-06-12-232532_framed.png
--------------------------------------------------------------------------------
/screenshots/screenrecord.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/screenshots/screenrecord.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "AmazingAvatar"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/web_hi_res_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hearsilent/AmazingAvatar/6225da9b320b52923936ebf23f6208dae3e92b3f/web_hi_res_512.png
--------------------------------------------------------------------------------