Add error messages instead of toasts

This commit is contained in:
Matthieu 2020-10-31 11:21:56 +01:00
parent 40f4d03bd1
commit 2e88e49875
19 changed files with 306 additions and 67 deletions

View File

@ -66,15 +66,15 @@ dependencies {
*/ */
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment:2.3.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui:2.3.0' implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
implementation "androidx.browser:browser:1.2.0" implementation "androidx.browser:browser:1.2.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
implementation 'androidx.paging:paging-runtime-ktx:2.1.2' implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
@ -83,14 +83,14 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
// Use the most recent version of CameraX // Use the most recent version of CameraX
def camerax_version = '1.0.0-beta08' def camerax_version = '1.0.0-beta11'
implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}"
// CameraX Lifecycle library // CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class // CameraX View class
implementation 'androidx.camera:camera-view:1.0.0-alpha15' implementation 'androidx.camera:camera-view:1.0.0-alpha18'
def room_version = "2.2.5" def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
@ -106,13 +106,13 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'
//Dagger (dependency injection) //Dagger (dependency injection)
implementation 'com.google.dagger:dagger-android:2.28.3' implementation 'com.google.dagger:dagger-android:2.29.1'
implementation 'com.google.dagger:dagger-android-support:2.28.3' implementation 'com.google.dagger:dagger-android-support:2.29.1'
// if you use the support libraries // if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.28.3' kapt 'com.google.dagger:dagger-android-processor:2.29.1'
kapt 'com.google.dagger:dagger-compiler:2.28.3' kapt 'com.google.dagger:dagger-compiler:2.29.1'
implementation 'com.squareup.okhttp3:okhttp:4.8.1' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
@ -160,9 +160,9 @@ dependencies {
debugImplementation "androidx.fragment:fragment-testing:1.2.5" debugImplementation "androidx.fragment:fragment-testing:1.2.5"
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.27.1' testImplementation 'com.github.tomakehurst:wiremock-jre8:2.27.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
testImplementation "androidx.room:room-testing:$room_version" testImplementation "androidx.room:room-testing:$room_version"
@ -175,7 +175,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
androidTestImplementation('com.squareup.okhttp3:mockwebserver:4.8.0') androidTestImplementation('com.squareup.okhttp3:mockwebserver:4.9.0')
} }

View File

@ -220,7 +220,7 @@ class CameraFragment : Fragment() {
) )
// Attach the viewfinder's surface provider to preview use case // Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider()) preview?.setSurfaceProvider(viewFinder.surfaceProvider)
} catch (exc: Exception) { } catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc) Log.e(TAG, "Use case binding failed", exc)
} }

View File

@ -9,28 +9,27 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.textview.MaterialTextView
import com.h.pixeldroid.Pixeldroid import com.h.pixeldroid.Pixeldroid
import com.h.pixeldroid.PostActivity import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.SearchActivity
import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.db.AppDatabase
import com.h.pixeldroid.di.PixelfedAPIHolder import com.h.pixeldroid.di.PixelfedAPIHolder
import com.h.pixeldroid.objects.DiscoverPost import com.h.pixeldroid.objects.DiscoverPost
import com.h.pixeldroid.objects.DiscoverPosts import com.h.pixeldroid.objects.DiscoverPosts
import com.h.pixeldroid.objects.Status import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.DBUtils
import com.h.pixeldroid.utils.ImageConverter import com.h.pixeldroid.utils.ImageConverter
import com.mikepenz.iconics.IconicsColor
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.padding import com.mikepenz.iconics.utils.color
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.paddingDp import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.*
@ -85,6 +84,7 @@ class SearchDiscoverFragment : Fragment() {
discoverText.setCompoundDrawables(IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_explore).apply { discoverText.setCompoundDrawables(IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_explore).apply {
sizeDp = 24 sizeDp = 24
paddingDp = 20 paddingDp = 20
color = IconicsColor.colorRes(R.color.colorDrawing)
}, null, null, null) }, null, null, null)
return view return view
@ -107,12 +107,24 @@ class SearchDiscoverFragment : Fragment() {
} }
} }
fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
if(show){
motionLayout.transitionToEnd()
} else {
motionLayout.transitionToStart()
}
discoverRefreshLayout.isRefreshing = false
discoverProgressBar.visibility = View.GONE
}
private fun getDiscover() { private fun getDiscover() {
api.discover("Bearer $accessToken") api.discover("Bearer $accessToken")
.enqueue(object : Callback<DiscoverPosts> { .enqueue(object : Callback<DiscoverPosts> {
override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) { override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) {
showError()
Log.e("SearchDiscoverFragment:", t.toString()) Log.e("SearchDiscoverFragment:", t.toString())
} }
@ -120,8 +132,10 @@ class SearchDiscoverFragment : Fragment() {
if(response.code() == 200) { if(response.code() == 200) {
val discoverPosts = response.body()!! val discoverPosts = response.body()!!
adapter.addPosts(discoverPosts.posts) adapter.addPosts(discoverPosts.posts)
discoverProgressBar.visibility = View.GONE showError(show = false)
discoverRefreshLayout.isRefreshing = false }
else {
showError()
} }
} }
}) })

View File

@ -72,6 +72,8 @@ open class AccountListFragment : FeedFragment() {
}) })
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
showError(show = false)
//by invalidating data, loadInitial will be called again //by invalidating data, loadInitial will be called again
factory.liveData.value!!.invalidate() factory.liveData.value!!.invalidate()
} }
@ -135,14 +137,14 @@ open class AccountListFragment : FeedFragment() {
val data = response.body()!! val data = response.body()!!
callback.onResult(data) callback.onResult(data)
} else{ } else{
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<List<Account>>, t: Throwable) { override fun onFailure(call: Call<List<Account>>, t: Throwable) {
Toast.makeText(context, getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("AccountListFragment", t.toString()) Log.e("AccountListFragment", t.toString())
} }
}) })

View File

@ -1,11 +1,14 @@
package com.h.pixeldroid.fragments.feeds package com.h.pixeldroid.fragments.feeds
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource import androidx.paging.DataSource
@ -22,6 +25,7 @@ import com.h.pixeldroid.db.AppDatabase
import com.h.pixeldroid.db.UserDatabaseEntity import com.h.pixeldroid.db.UserDatabaseEntity
import com.h.pixeldroid.di.PixelfedAPIHolder import com.h.pixeldroid.di.PixelfedAPIHolder
import com.h.pixeldroid.objects.FeedContent import com.h.pixeldroid.objects.FeedContent
import kotlinx.android.synthetic.main.fragment_feed.*
import kotlinx.android.synthetic.main.fragment_feed.view.* import kotlinx.android.synthetic.main.fragment_feed.view.*
import retrofit2.Call import retrofit2.Call
import javax.inject.Inject import javax.inject.Inject
@ -65,6 +69,16 @@ open class FeedFragment: Fragment() {
return view return view
} }
fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
if(show){
errorLayout.visibility = VISIBLE
progressBar.visibility = GONE
} else {
errorLayout.visibility = GONE
progressBar.visibility = VISIBLE
}
}
open inner class FeedDataSourceFactory<ObjectId, APIObject: FeedContent>( open inner class FeedDataSourceFactory<ObjectId, APIObject: FeedContent>(
private val dataSource: FeedDataSource<ObjectId, APIObject> private val dataSource: FeedDataSource<ObjectId, APIObject>
): DataSource.Factory<ObjectId, APIObject>() { ): DataSource.Factory<ObjectId, APIObject>() {

View File

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
@ -86,6 +85,8 @@ class NotificationsFragment : FeedFragment() {
}) })
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
showError(show = false)
//by invalidating data, loadInitial will be called again //by invalidating data, loadInitial will be called again
factory.liveData.value!!.invalidate() factory.liveData.value!!.invalidate()
} }
@ -125,15 +126,15 @@ class NotificationsFragment : FeedFragment() {
if (response.isSuccessful && response.body() != null) { if (response.isSuccessful && response.body() != null) {
val data = response.body()!! val data = response.body()!!
callback.onResult(data) callback.onResult(data)
} else{ } else {
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<List<Notification>>, t: Throwable) { override fun onFailure(call: Call<List<Notification>>, t: Throwable) {
Toast.makeText(context, getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("NotificationsFragment", t.toString()) Log.e("NotificationsFragment", t.toString())
} }
}) })

View File

@ -53,15 +53,15 @@ class HomeTimelineFragment: PostsFeedFragment() {
val notifications = response.body()!! val notifications = response.body()!!
callback.onResult(notifications) callback.onResult(notifications)
DBUtils.storePosts(db, notifications, user!!) DBUtils.storePosts(db, notifications, user!!)
} else{ } else {
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<List<Status>>, t: Throwable) { override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Toast.makeText(context, getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("PostsFeedFragment", t.toString()) Log.e("PostsFeedFragment", t.toString())
} }
}) })

View File

@ -68,6 +68,8 @@ abstract class PostsFeedFragment : FeedFragment() {
}) })
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
showError(show = false)
//by invalidating data, loadInitial will be called again //by invalidating data, loadInitial will be called again
factory.liveData.value!!.invalidate() factory.liveData.value!!.invalidate()
} }

View File

@ -34,14 +34,14 @@ class PublicTimelineFragment: PostsFeedFragment() {
val notifications = response.body()!! val notifications = response.body()!!
callback.onResult(notifications) callback.onResult(notifications)
} else{ } else{
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<List<Status>>, t: Throwable) { override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Toast.makeText(context, getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("PublicTimelineFragment", t.toString()) Log.e("PublicTimelineFragment", t.toString())
} }
}) })

View File

@ -77,14 +77,14 @@ class SearchAccountFragment: AccountListFragment(){
callback.onResult(notifications as List<Account>) callback.onResult(notifications as List<Account>)
} else{ } else{
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<Results>, t: Throwable) { override fun onFailure(call: Call<Results>, t: Throwable) {
Toast.makeText(context,getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("FeedFragment", t.toString()) Log.e("FeedFragment", t.toString())
} }
}) })

View File

@ -59,6 +59,8 @@ class SearchHashtagFragment: FeedFragment(){
}) })
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
showError(show = false)
//by invalidating data, loadInitial will be called again //by invalidating data, loadInitial will be called again
factory.liveData.value!!.invalidate() factory.liveData.value!!.invalidate()
} }
@ -107,14 +109,14 @@ class SearchHashtagFragment: FeedFragment(){
callback.onResult(notifications as List<Tag>) callback.onResult(notifications as List<Tag>)
} else{ } else{
Toast.makeText(context,getString(R.string.loading_toast), Toast.LENGTH_SHORT).show() showError()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<Results>, t: Throwable) { override fun onFailure(call: Call<Results>, t: Throwable) {
Toast.makeText(context,getString(R.string.feed_failed), Toast.LENGTH_SHORT).show() showError(errorText = R.string.feed_failed)
Log.e("FeedFragment", t.toString()) Log.e("FeedFragment", t.toString())
} }
}) })

View File

@ -8,6 +8,7 @@ import android.view.ViewGroup
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList import androidx.paging.PagedList
import com.h.pixeldroid.R
import com.h.pixeldroid.fragments.feeds.FeedFragment import com.h.pixeldroid.fragments.feeds.FeedFragment
import com.h.pixeldroid.fragments.feeds.postFeeds.PostsFeedFragment import com.h.pixeldroid.fragments.feeds.postFeeds.PostsFeedFragment
import com.h.pixeldroid.objects.Results import com.h.pixeldroid.objects.Results
@ -68,15 +69,15 @@ class SearchPostsFragment: PostsFeedFragment(){
if (response.code() == 200) { if (response.code() == 200) {
val notifications = response.body()!!.statuses as ArrayList<Status> val notifications = response.body()!!.statuses as ArrayList<Status>
callback.onResult(notifications as List<Status>) callback.onResult(notifications as List<Status>)
} else {
} else{ showError()
Log.e("FeedFragment", "got response code ${response.code()}")
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadingIndicator.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<Results>, t: Throwable) { override fun onFailure(call: Call<Results>, t: Throwable) {
showError(errorText = R.string.feed_failed)
Log.e("FeedFragment", t.toString()) Log.e("FeedFragment", t.toString())
} }
}) })

View File

@ -0,0 +1,81 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="80dp"
android:viewportWidth="64"
android:viewportHeight="80">
<path
android:fillColor="#FF000000"
android:pathData="M42,54H40c0,-5.646 -7.311,-14 -23,-14V38C34.242,38 42,47.356 42,54Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M21.0394,38.7261l1.9971,-7.0007l1.9242,0.5489l-1.9971,7.0007z"/>
<path
android:fillColor="#FF000000"
android:pathData="M32,24a2.025,2.025 0,0 1,-1.568 -0.721l-1,-1.2a1.818,1.818 0,0 1,-0.26 -1.954A1.984,1.984 0,0 1,31 19h2a1.984,1.984 0,0 1,1.827 1.127,1.818 1.818,0 0,1 -0.259,1.953l-1,1.2A2.026,2.026 0,0 1,32 24ZM31.132,21 L31.968,22 32.905,20.943Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M37.394,20a1.376,1.376 0,0 1,-1 -0.4c-0.669,-0.669 -0.48,-1.829 0.448,-2.756s2.087,-1.117 2.756,-0.448 0.48,1.829 -0.448,2.756A2.589,2.589 0,0 1,37.394 20Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M26.606,20a2.589,2.589 0,0 1,-1.76 -0.846c-0.928,-0.927 -1.117,-2.087 -0.448,-2.756s1.828,-0.481 2.756,0.448 1.117,2.087 0.448,2.756A1.376,1.376 0,0 1,26.606 20Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M50.707,15.707l-1.414,-1.414c2,-2 2.595,-8.817 2.691,-12.293H50c-7.287,0 -9.3,1.71 -9.314,1.728a1.039,1.039 0,0 1,-1.031 0.21A19.494,19.494 0,0 0,33 3H31V1h2a22.413,22.413 0,0 1,6.834 0.886C40.9,1.216 43.713,0 50,0h3a1,1 0,0 1,1 1C54,2.168 53.907,12.507 50.707,15.707Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M34,34H30V32h4c14.617,0 18.019,-6.558 18.788,-9.11a4.9,4.9 0,0 1,-2.495 -1.183A1,1 0,0 1,51 20a3.81,3.81 0,0 0,2.221 -1.388c-2.275,-0.976 -4.387,-3.662 -6.6,-6.472l-0.41,-0.521A1,1 0,0 1,46 11C46,8.514 49.383,3.2 52.293,0.293l1.414,1.414c-2.766,2.766 -5.38,7.168 -5.679,8.982l0.169,0.215C50.444,13.762 52.991,17 55,17a1,1 0,0 1,0.832 1.555,10.9 10.9,0 0,1 -2.254,2.426 1.7,1.7 0,0 1,1.16 0.344,1 1,0 0,1 0.258,0.766C54.952,22.577 53.687,34 34,34Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M13.293,15.707C10.093,12.507 10,2.168 10,1a1,1 0,0 1,1 -1h3c6.287,0 9.1,1.216 10.166,1.886A22.413,22.413 0,0 1,31 1V3a19.494,19.494 0,0 0,-6.655 0.938,0.992 0.992,0 0,1 -1.052,-0.231h0S21.281,2 14,2H12.016c0.1,3.476 0.689,10.291 2.691,12.293Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M30,34C10.313,34 9.048,22.577 9,22.091A1,1 0,0 1,10 21c0.147,0 0.289,-0.007 0.422,-0.019a10.9,10.9 0,0 1,-2.254 -2.426A1,1 0,0 1,9 17c2.009,0 4.556,-3.238 6.8,-6.1l0.169,-0.215c-0.3,-1.814 -2.913,-6.216 -5.679,-8.982L11.707,0.293C14.617,3.2 18,8.514 18,11a1,1 0,0 1,-0.215 0.619l-0.41,0.521c-2.209,2.809 -4.319,5.494 -6.593,6.471A3.824,3.824 0,0 0,13 20a1,1 0,0 1,0.707 1.707,4.908 4.908,0 0,1 -2.5,1.184C11.979,25.438 15.377,32 30,32Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M40,64H14C4.972,64 0,59.383 0,51c0,-7.411 7.309,-13 17,-13v2C8.448,40 2,44.729 2,51c0,9.092 6.525,11 12,11H40c4.087,0 10,-2.493 10,-7V53c0,-1.638 -0.424,-2.351 -1.469,-3.949a0.8,0.8 0,0 1,-0.054 -0.095l-8.369,-16.5 1.784,-0.9L50.235,48A7.987,7.987 0,0 1,52 53v2C52,60.872 45.031,64 40,64Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M17.99,11.109A0.518,0.518 0,0 0,18 11H16c0,-1.488 5.386,-9 8,-9V4C22.744,4.113 18.309,9.836 17.99,11.109Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M46.01,11.109c-0.319,-1.273 -4.754,-7 -6.017,-7.109L40,2c2.614,0 8,7.512 8,9H46A0.518,0.518 0,0 0,46.01 11.109Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M32,34c-3.5,0 -10,0 -10,-6 0,-3.406 4.336,-7.849 8.754,-8.97l0.492,1.94C27.514,21.916 24,25.732 24,28c0,3.7 3.623,4 8,4s8,-0.3 8,-4c0,-2.265 -3.514,-6.081 -7.247,-7.031l0.494,-1.938C37.664,20.155 42,24.6 42,28 42,34 35.495,34 32,34Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M37,16a2.754,2.754 0,0 1,-3 -2.5A2.754,2.754 0,0 1,37 11a2.754,2.754 0,0 1,3 2.5A2.754,2.754 0,0 1,37 16ZM37,13c-0.62,0 -1,0.323 -1,0.5s0.38,0.5 1,0.5 1,-0.323 1,-0.5S37.62,13 37,13Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M27,16a2.754,2.754 0,0 1,-3 -2.5A2.754,2.754 0,0 1,27 11a2.754,2.754 0,0 1,3 2.5A2.754,2.754 0,0 1,27 16ZM27,13c-0.62,0 -1,0.323 -1,0.5s0.38,0.5 1,0.5 1,-0.323 1,-0.5S27.62,13 27,13Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M42.956,30a1,1 0,0 1,-0.875 -1.485c0.789,-1.421 1.11,-2.244 0.932,-3.357 -0.1,-0.632 -0.306,-1.075 -0.547,-1.187a2.132,2.132 0,0 1,-0.819 -0.721,3.262 3.262,0 0,1 -0.476,-2.984c0.354,-1.034 1.3,-2.266 3.8,-2.266C47.683,18 49,19.517 49,22.637 49,26.6 45.677,30 42.956,30ZM44.974,20c-1.6,0 -1.818,0.642 -1.912,0.916a1.263,1.263 0,0 0,0.2 1.153,0.654 0.654,0 0,0 0.1,0.121 3.237,3.237 0,0 1,1.623 2.653,5.327 5.327,0 0,1 -0.109,2.264A6.114,6.114 0,0 0,47 22.637C47,20.184 46.2,20 44.974,20Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M21.044,30C18.323,30 15,26.6 15,22.637 15,19.517 16.317,18 19.026,18c2.5,0 3.449,1.232 3.8,2.266a3.265,3.265 0,0 1,-0.478 2.987,2.135 2.135,0 0,1 -0.823,0.721c-0.235,0.109 -0.44,0.552 -0.541,1.183 -0.178,1.114 0.143,1.937 0.932,3.358A1,1 0,0 1,21.044 30ZM19.026,20C17.8,20 17,20.184 17,22.637a6.114,6.114 0,0 0,2.122 4.47,5.332 5.332,0 0,1 -0.109,-2.265 3.285,3.285 0,0 1,1.652 -2.672s0.026,-0.036 0.07,-0.1a1.265,1.265 0,0 0,0.2 -1.156C20.844,20.642 20.623,20 19.026,20Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M31,23h2v2h-2z"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.445,27.832 L32,26.2l-2.445,1.63 -1.11,-1.664 3,-2a1,1 0,0 1,1.11 0l3,2Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14,64c-4.363,0 -9,-4.557 -9,-13 0,-5.508 2.054,-10 5.494,-12.009l1.01,1.727C8.684,42.366 7,46.21 7,51c0,6.886 3.56,11 7,11Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M22,64c-4.363,0 -9,-4.557 -9,-13s4.637,-13 9,-13v2c-3.44,0 -7,4.114 -7,11s3.56,11 7,11Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M29,64c-4.363,0 -9,-4.557 -9,-13 0,-5.508 2.054,-10 5.494,-12.009l1.01,1.727C23.684,42.366 22,46.21 22,51c0,6.886 3.56,11 7,11Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M37,64c-4.363,0 -9,-4.557 -9,-13 0,-4.888 1.539,-7.923 2.83,-9.608l1.588,1.216C31.314,44.048 30,46.673 30,51c0,6.886 3.56,11 7,11Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M45,63c-4.479,0 -9,-5.257 -9,-17h2c0,9.848 3.521,15 7,15Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M37,36h2v11h-2z"/>
</vector>

View File

@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/list" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" app:layoutManager="LinearLayoutManager" />
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -24,5 +24,52 @@
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" /> app:layout_constraintBottom_toBottomOf="parent"
</androidx.coordinatorlayout.widget.CoordinatorLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/errorLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:id="@+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/colorDrawing"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/red_panda" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Something went wrong..."
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView4" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This panda is not happy. Pull to refresh to try again."
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -40,24 +40,71 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motionLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> app:layoutDescription="@xml/fragment_search_xml_error_scene"
app:layout_constraintBottom_toTopOf="@+id/discoverList"
app:layout_constraintTop_toTopOf="@+id/discoverList">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/discoverErrorLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/red_panda"
app:tint="@color/colorDrawing" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Something went wrong..."
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This panda is not happy. Pull to refresh to try again."
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView <TextView
android:id="@+id/discoverText" android:id="@+id/discoverText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/discover" android:text="@string/discover"
android:layout_gravity="center_horizontal"/> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/discoverErrorLayout" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverList" android:id="@+id/discoverList"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"/> android:nestedScrollingEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/discoverText" />
</LinearLayout> </androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -2,15 +2,8 @@
<resources> <resources>
<color name="colorPrimary">#FFFFFF</color> <color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryTab">#6200EE</color> <color name="colorPrimaryTab">#6200EE</color>
<color name="colorPrimaryActionBar">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="colorButtonBg">#6200EE</color>
<color name="colorButtonText">#FFFFFF</color>
<color name="filterLabelNormal">#8A8889</color>
<color name="filterLabelSelected">#221F20</color>
<color name="colorPrimaryError">#FF0000</color>
<color name="colorText">#000000</color> <color name="colorText">#000000</color>
<color name="colorDrawing">#FFFFFF</color>
</resources> </resources>

View File

@ -14,4 +14,5 @@
<color name="filterLabelSelected">#221F20</color> <color name="filterLabelSelected">#221F20</color>
<color name="colorPrimaryError">#FF0000</color> <color name="colorPrimaryError">#FF0000</color>
<color name="colorText">#FFFFFF</color> <color name="colorText">#FFFFFF</color>
<color name="colorDrawing">#000000</color>
</resources> </resources>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<Transition
android:id="@+id/transition"
app:constraintSetEnd="@+id/end"
app:constraintSetStart="@id/start"
app:duration="500">
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/discoverErrorLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/discoverErrorLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
</MotionScene>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.0' ext.kotlin_version = '1.4.10'
repositories { repositories {
google() google()
jcenter() jcenter()