├── app ├── .gitignore ├── release │ └── app-release.aab ├── src │ ├── main │ │ ├── res │ │ │ ├── font │ │ │ │ ├── roboto_bold.ttf │ │ │ │ ├── roboto_thin.ttf │ │ │ │ ├── roboto_light.ttf │ │ │ │ ├── roboto_medium.ttf │ │ │ │ └── roboto_regular.ttf │ │ │ ├── drawable │ │ │ │ ├── app_icon.png │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── themes.xml │ │ │ │ └── colors.xml │ │ │ ├── mipmap-anydpi-v33 │ │ │ │ └── ic_launcher.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── raw │ │ │ │ ├── loading_icon.json │ │ │ │ ├── favorite_icon.json │ │ │ │ └── empty_icon.json │ │ ├── java │ │ │ └── com │ │ │ │ └── yvkalume │ │ │ │ └── gifapp │ │ │ │ ├── di │ │ │ │ ├── mavericks │ │ │ │ │ ├── MavericksViewModelScoped.kt │ │ │ │ │ ├── ViewModelScopedClass.kt │ │ │ │ │ ├── ViewModelKey.kt │ │ │ │ │ ├── AssistedViewModelFactory.kt │ │ │ │ │ └── HiltMavericksViewModelFactory.kt │ │ │ │ └── ViewModelModule.kt │ │ │ │ ├── ui │ │ │ │ ├── util │ │ │ │ │ ├── Utils.kt │ │ │ │ │ ├── Destination.kt │ │ │ │ │ └── Modifier.kt │ │ │ │ ├── screen │ │ │ │ │ ├── home │ │ │ │ │ │ ├── logic │ │ │ │ │ │ │ ├── HomeUiState.kt │ │ │ │ │ │ │ └── HomeViewModel.kt │ │ │ │ │ │ └── HomeScreen.kt │ │ │ │ │ ├── favorite │ │ │ │ │ │ ├── logic │ │ │ │ │ │ │ ├── FavoriteUiState.kt │ │ │ │ │ │ │ └── FavoriteViewModel.kt │ │ │ │ │ │ └── FavoriteScreen.kt │ │ │ │ │ └── root │ │ │ │ │ │ └── RootScreen.kt │ │ │ │ ├── theme │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ └── components │ │ │ │ │ ├── LikeIcon.kt │ │ │ │ │ ├── EmptyView.kt │ │ │ │ │ ├── LoadingView.kt │ │ │ │ │ ├── GifListView.kt │ │ │ │ │ ├── CustomImageView.kt │ │ │ │ │ └── GifItem.kt │ │ │ │ ├── app │ │ │ │ ├── App.kt │ │ │ │ └── GifAppState.kt │ │ │ │ ├── util │ │ │ │ ├── ImageCacheInterceptor.kt │ │ │ │ └── Extensions.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── yvkalume │ │ │ └── gifapp │ │ │ └── ExampleInstrumentedTest.kt │ └── test │ │ └── java │ │ └── com │ │ └── yvkalume │ │ └── gifapp │ │ └── ui │ │ └── screen │ │ └── home │ │ └── logic │ │ └── HomeViewModelTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── data ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── yvkalume │ │ │ └── gifapp │ │ │ └── data │ │ │ ├── model │ │ │ ├── mapper │ │ │ │ ├── Mapper.kt │ │ │ │ ├── GifMapper.kt │ │ │ │ └── GifEntityMapper.kt │ │ │ ├── server │ │ │ │ ├── GiphyItem.kt │ │ │ │ ├── MetaObject.kt │ │ │ │ ├── PaginationObject.kt │ │ │ │ ├── GifImage.kt │ │ │ │ └── GiphyHttpResponse.kt │ │ │ └── room │ │ │ │ └── GifEntity.kt │ │ │ ├── util │ │ │ ├── ServerEndpoints.kt │ │ │ ├── DispacherAnnotation.kt │ │ │ └── Result.kt │ │ │ ├── room │ │ │ ├── migrations │ │ │ │ ├── MIGRATION_2_3.kt │ │ │ │ └── MIGRATION_1_2.kt │ │ │ ├── AppDataBase.kt │ │ │ └── dao │ │ │ │ └── GifDao.kt │ │ │ ├── datasource │ │ │ ├── GifRemoteDataSource.kt │ │ │ └── GifLocalDataSource.kt │ │ │ ├── di │ │ │ ├── CoroutineModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ ├── RoomModule.kt │ │ │ └── DataSourceModule.kt │ │ │ └── repository │ │ │ └── GifRepositoryImpl.kt │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── yvkalume │ │ │ └── gifapp │ │ │ └── data │ │ │ └── ExampleInstrumentedTest.kt │ └── test │ │ └── java │ │ └── com │ │ └── yvkalume │ │ └── gifapp │ │ └── data │ │ └── model │ │ └── mapper │ │ └── GifEntityMapperTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── domain ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── yvkalume │ │ │ └── gifapp │ │ │ └── domain │ │ │ ├── entity │ │ │ └── Gif.kt │ │ │ └── repository │ │ │ └── GifRepository.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── yvkalume │ │ │ └── gifapp │ │ │ └── domain │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── yvkalume │ │ └── gifapp │ │ └── domain │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── secrets.sample.properties ├── preview ├── preview1.png ├── preview2.png └── preview3.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libraries.versions.toml ├── .gitignore ├── settings.gradle.kts ├── .github └── workflows │ └── android.yml ├── gradle.properties ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /secrets.sample.properties: -------------------------------------------------------------------------------- 1 | giphyApiKey=YOUR_API_KEY -------------------------------------------------------------------------------- /preview/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/preview/preview1.png -------------------------------------------------------------------------------- /preview/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/preview/preview2.png -------------------------------------------------------------------------------- /preview/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/preview/preview3.png -------------------------------------------------------------------------------- /app/release/app-release.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/release/app-release.aab -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/font/roboto_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/font/roboto_thin.ttf -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/font/roboto_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/font/roboto_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yveskalume/gif-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /secrets.properties 5 | .idea 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/mavericks/MavericksViewModelScoped.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di.mavericks 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class MavericksViewModelScoped -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 21 14:53:16 CAT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/yvkalume/gifapp/domain/entity/Gif.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.domain.entity 2 | 3 | data class Gif( 4 | val id: String, 5 | val title: String, 6 | val imageUrl: String, 7 | val isFavorite: Boolean, 8 | val createdAt: Long, 9 | val updatedAt: Long 10 | ) 11 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/mapper/Mapper.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.mapper 2 | 3 | abstract class Mapper { 4 | abstract fun map(input: I): O 5 | 6 | fun mapList(inputs: List): List { 7 | return inputs.map { item -> this.map(item) } 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GifApp 3 | 4 | 5 | Stickers 6 | Gifs 7 | 8 | 9 | Home 10 | Favorites 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.util 2 | 3 | import android.app.DownloadManager 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.net.Uri 7 | import android.os.Environment 8 | import androidx.activity.compose.ManagedActivityResultLauncher 9 | import androidx.core.content.ContextCompat -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/home/logic/HomeUiState.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.home.logic 2 | 3 | import com.airbnb.mvrx.Async 4 | import com.airbnb.mvrx.Loading 5 | import com.airbnb.mvrx.MavericksState 6 | import com.yvkalume.gifapp.domain.entity.Gif 7 | 8 | data class HomeUiState( 9 | val gifs: Async> = Loading(), 10 | ) : MavericksState -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/util/ServerEndpoints.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.util 2 | 3 | import com.yvkalume.gifapp.data.BuildConfig 4 | 5 | object ServerEndpoints { 6 | 7 | private const val baseUrl = "https://api.giphy.com/v1" 8 | 9 | const val trendingGifs = 10 | "$baseUrl/stickers/trending?api_key=${BuildConfig.giphyApiKey}&limit=25" 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/favorite/logic/FavoriteUiState.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.favorite.logic 2 | 3 | import com.airbnb.mvrx.Async 4 | import com.airbnb.mvrx.Loading 5 | import com.airbnb.mvrx.MavericksState 6 | import com.yvkalume.gifapp.domain.entity.Gif 7 | 8 | data class FavoriteUiState( 9 | val gifs: Async> = Loading(), 10 | ) : MavericksState -------------------------------------------------------------------------------- /domain/src/main/java/com/yvkalume/gifapp/domain/repository/GifRepository.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.domain.repository 2 | 3 | import com.yvkalume.gifapp.domain.entity.Gif 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface GifRepository { 7 | fun getAllTrending(): Flow> 8 | suspend fun update(gif: Gif) 9 | fun getFavorites(): Flow> 10 | suspend fun refresh() 11 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/room/migrations/MIGRATION_2_3.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.room.migrations 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | val MIGRATION_2_3 = object : Migration(2, 3) { 7 | override fun migrate(database: SupportSQLiteDatabase) { 8 | database.execSQL("DROP TABLE stickers") 9 | } 10 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/server/GiphyItem.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.server 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | @kotlinx.serialization.Serializable 6 | data class GiphyItem( 7 | @SerialName("id") 8 | val id: String = "", 9 | @SerialName("title") 10 | val title: String = "", 11 | @SerialName("images") 12 | val images: GifImage 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/server/MetaObject.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.server 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | @kotlinx.serialization.Serializable 6 | data class MetaObject( 7 | @SerialName("msg") 8 | val msg: String = "", 9 | @SerialName("status") 10 | val status: Int, 11 | @SerialName("response_id") 12 | val response_id: String = "" 13 | ) 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/server/PaginationObject.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.server 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | @kotlinx.serialization.Serializable 6 | data class PaginationObject( 7 | @SerialName("offset") 8 | val offset: Int, 9 | @SerialName("total_count") 10 | val total_count: Int, 11 | @SerialName("count") 12 | val count: Int 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/mavericks/ViewModelScopedClass.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di.mavericks 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | import javax.inject.Inject 5 | 6 | @MavericksViewModelScoped 7 | class ViewModelScopedClass @Inject constructor() { 8 | val id = instanceId.incrementAndGet() 9 | 10 | companion object { 11 | private val instanceId = AtomicInteger(0) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/server/GifImage.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.server 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | @kotlinx.serialization.Serializable 6 | data class GifImage( 7 | @SerialName("fixed_height") 8 | val fixed_height: FixedHeight 9 | ) 10 | 11 | @kotlinx.serialization.Serializable 12 | data class FixedHeight( 13 | @SerialName("url") 14 | val url: String = "" 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.app 2 | 3 | import android.app.Application 4 | import com.airbnb.mvrx.Mavericks 5 | import dagger.hilt.android.HiltAndroidApp 6 | import timber.log.Timber 7 | 8 | @HiltAndroidApp 9 | class App : Application() { 10 | override fun onCreate() { 11 | super.onCreate() 12 | Mavericks.initialize(this) 13 | Timber.plant(Timber.DebugTree()) 14 | } 15 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/room/AppDataBase.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.room 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.yvkalume.gifapp.data.model.room.GifEntity 6 | import com.yvkalume.gifapp.data.room.dao.GifDao 7 | 8 | @Database(entities = [GifEntity::class], version = 3, exportSchema = false) 9 | abstract class AppDatabase : RoomDatabase() { 10 | abstract fun gifDao(): GifDao 11 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/server/GiphyHttpResponse.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.server 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | @kotlinx.serialization.Serializable 6 | data class GiphyHttpResponse( 7 | @SerialName("data") 8 | val data: List = emptyList(), 9 | @SerialName("pagination") 10 | val pagination: PaginationObject, 11 | @SerialName("meta") 12 | val meta: MetaObject 13 | ) 14 | -------------------------------------------------------------------------------- /domain/src/test/java/com/yvkalume/gifapp/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/mavericks/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di.mavericks 2 | 3 | 4 | import com.airbnb.mvrx.MavericksViewModel 5 | import dagger.MapKey 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * A [MapKey] for populating a map of ViewModels and their factories. 10 | */ 11 | @Retention(AnnotationRetention.RUNTIME) 12 | @Target(AnnotationTarget.FUNCTION) 13 | @MapKey 14 | annotation class ViewModelKey(val value: KClass>) -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Green200 = Color(0xFF009688) 6 | val Green300 = Color(0xFF00796B) 7 | val LightGreen = Color(0xFFB2DFDB) 8 | 9 | val White = Color(0xFFFFFFFF) 10 | 11 | val Blue200 = Color(0xFF4688F4) 12 | val Blue300 = Color(0xFF3D6EC9) 13 | 14 | val Black100 = Color(0xFF212121) 15 | 16 | val Gray200 = Color(0xFF757575) 17 | val Gray100 = Color(0xFFBDBDBD) 18 | val Gray002 = Color.LightGray.copy(alpha = 0.4f) 19 | val Gray004 = Color.LightGray.copy(alpha = 0.8f) -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | 15 | versionCatalogs { 16 | create("libs") { 17 | from(files("./gradle/libraries.versions.toml")) 18 | } 19 | } 20 | } 21 | rootProject.name = "GifApp" 22 | 23 | include(":app") 24 | include(":data") 25 | include(":domain") 26 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/datasource/GifRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.datasource 2 | 3 | import com.yvkalume.gifapp.data.model.server.GiphyHttpResponse 4 | import com.yvkalume.gifapp.data.util.ServerEndpoints 5 | import io.ktor.client.HttpClient 6 | import io.ktor.client.call.body 7 | import io.ktor.client.request.get 8 | import javax.inject.Inject 9 | 10 | class GifRemoteDataSource @Inject constructor(private val httpClient: HttpClient) { 11 | 12 | suspend fun getAllTrending(): GiphyHttpResponse { 13 | return httpClient.get(ServerEndpoints.trendingGifs).body() 14 | } 15 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/util/DispacherAnnotation.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.util 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class DefaultDispatcher 8 | 9 | @Qualifier 10 | @Retention(AnnotationRetention.BINARY) 11 | annotation class IoDispatcher 12 | 13 | @Qualifier 14 | @Retention(AnnotationRetention.BINARY) 15 | annotation class MainDispatcher 16 | 17 | @Qualifier 18 | @Retention(AnnotationRetention.BINARY) 19 | annotation class MainImmediateDispatcher 20 | 21 | @Qualifier 22 | @Retention(AnnotationRetention.BINARY) 23 | annotation class ApplicationScope -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/mapper/GifMapper.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.mapper 2 | 3 | import com.yvkalume.gifapp.data.model.room.GifEntity 4 | import com.yvkalume.gifapp.domain.entity.Gif 5 | 6 | object GifMapper : Mapper() { 7 | override fun map(input: GifEntity): Gif { 8 | return with(input) { 9 | Gif( 10 | id = id, 11 | title = title, 12 | imageUrl = imageUrl, 13 | isFavorite = isFavorite, 14 | createdAt = createdAt, 15 | updatedAt = updatedAt 16 | ) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/mapper/GifEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.mapper 2 | 3 | import com.yvkalume.gifapp.data.model.room.GifEntity 4 | import com.yvkalume.gifapp.data.model.server.GiphyItem 5 | 6 | object GifEntityMapper : Mapper() { 7 | override fun map(input: GiphyItem): GifEntity { 8 | return with(input) { 9 | GifEntity( 10 | id = id, 11 | title = title, 12 | imageUrl = images.fixed_height.url, 13 | createdAt = System.currentTimeMillis(), 14 | updatedAt = System.currentTimeMillis() 15 | ) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yvkalume/gifapp/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.yvkalume.gifapp", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/room/migrations/MIGRATION_1_2.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.room.migrations 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | val MIGRATION_1_2 = object : Migration(1, 2) { 7 | override fun migrate(database: SupportSQLiteDatabase) { 8 | val timeStamp = System.currentTimeMillis() 9 | database.execSQL("ALTER TABLE gifs ADD COLUMN createdAt INTEGER NOT NULL DEFAULT $timeStamp") 10 | database.execSQL("ALTER TABLE gifs ADD COLUMN updatedAt INTEGER NOT NULL DEFAULT $timeStamp") 11 | 12 | database.execSQL("ALTER TABLE stickers ADD COLUMN createdAt INTEGER NOT NULL DEFAULT $timeStamp") 13 | database.execSQL("ALTER TABLE stickers ADD COLUMN updatedAt INTEGER NOT NULL DEFAULT $timeStamp") 14 | } 15 | } -------------------------------------------------------------------------------- /data/src/androidTest/java/com/yvkalume/gifapp/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.yvkalume.gifapp.data.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/yvkalume/gifapp/domain/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.domain 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.yvkalume.gifapp.domain.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/util/Result.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.util 2 | 3 | sealed class Result { 4 | 5 | data class Success(val data: T) : Result() 6 | data class Error(val exception: Exception) : Result() 7 | 8 | override fun toString(): String { 9 | return when (this) { 10 | is Success<*> -> "Success[data=$data]" 11 | is Error -> "Error[exception=$exception]" 12 | } 13 | } 14 | } 15 | 16 | /** 17 | * `true` if [Result] is of type [Success] & holds non-null [Success.data]. 18 | */ 19 | val Result<*>.succeeded 20 | get() = this is Result.Success && data != null 21 | 22 | fun Result.successOr(fallback: T): T { 23 | return (this as? Result.Success)?.data ?: fallback 24 | } 25 | 26 | 27 | 28 | val Result.data: T? 29 | get() = (this as? Result.Success)?.data -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: set up JDK 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Cache Gradle and wrapper 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.gradle/caches 25 | ~/.gradle/wrapper 26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gradle- 29 | - name: Make Gradle executable 30 | run: chmod +x ./gradlew 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew build -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/util/ImageCacheInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.util 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.util.LruCache 6 | import coil.decode.DataSource 7 | import coil.intercept.Interceptor 8 | import coil.request.ImageResult 9 | import coil.request.SuccessResult 10 | 11 | class ImageCacheInterceptor( 12 | private val context: Context, 13 | private val cache: LruCache 14 | ) : Interceptor { 15 | 16 | override suspend fun intercept(chain: Interceptor.Chain): ImageResult { 17 | val value = cache.get(chain.request.data.toString()) 18 | if (value != null) { 19 | return SuccessResult( 20 | drawable = value, 21 | request = chain.request, 22 | dataSource = DataSource.MEMORY_CACHE 23 | ) 24 | } 25 | return chain.proceed(chain.request) 26 | } 27 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/model/room/GifEntity.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.room 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.yvkalume.gifapp.domain.entity.Gif 7 | 8 | @Entity(tableName = "gifs") 9 | data class GifEntity( 10 | @PrimaryKey 11 | @ColumnInfo(name = "id") 12 | val id: String, 13 | @ColumnInfo(name = "title") 14 | val title: String, 15 | @ColumnInfo(name = "imageUrl") 16 | val imageUrl: String, 17 | @ColumnInfo(name = "isFavorite") 18 | val isFavorite: Boolean = false, 19 | @ColumnInfo(name = "createdAt") 20 | val createdAt: Long, 21 | @ColumnInfo(name = "updatedAt") 22 | val updatedAt: Long 23 | ) 24 | 25 | fun Gif.toEntity(): GifEntity { 26 | return GifEntity( 27 | id = id, 28 | title = title, 29 | imageUrl = imageUrl, 30 | isFavorite = isFavorite, 31 | createdAt = createdAt, 32 | updatedAt = updatedAt 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/di/CoroutineModule.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.di 2 | 3 | import com.yvkalume.gifapp.data.util.IoDispatcher 4 | import com.yvkalume.gifapp.data.util.MainDispatcher 5 | import com.yvkalume.gifapp.data.util.MainImmediateDispatcher 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import kotlinx.coroutines.CoroutineDispatcher 11 | import kotlinx.coroutines.Dispatchers 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object CoroutineModule { 16 | 17 | @Provides 18 | @IoDispatcher 19 | fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO 20 | 21 | @Provides 22 | @MainDispatcher 23 | fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main 24 | 25 | @Provides 26 | @MainImmediateDispatcher 27 | fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/mavericks/AssistedViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di.mavericks 2 | 3 | import com.airbnb.mvrx.MavericksState 4 | import com.airbnb.mvrx.MavericksViewModel 5 | 6 | /** 7 | * This factory allows Mavericks to supply the initial or restored [MavericksState] to Hilt. 8 | * 9 | * Add this interface inside of your [MavericksViewModel] class then create the following Hilt module: 10 | * 11 | * @Module 12 | * @InstallIn(MavericksViewModelComponent::class) 13 | * interface ViewModelsModule { 14 | * @Binds 15 | * @IntoMap 16 | * @ViewModelKey(MyViewModel::class) 17 | * fun myViewModelFactory(factory: MyViewModel.Factory): AssistedViewModelFactory<*, *> 18 | * } 19 | * 20 | * If you already have a ViewModelsModule then all you have to do is add the multibinding entry for your new [MavericksViewModel]. 21 | */ 22 | interface AssistedViewModelFactory, S : MavericksState> { 23 | fun create(state: S): VM 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di 2 | 3 | import com.yvkalume.gifapp.di.mavericks.AssistedViewModelFactory 4 | import com.yvkalume.gifapp.di.mavericks.MavericksViewModelComponent 5 | import com.yvkalume.gifapp.di.mavericks.ViewModelKey 6 | import com.yvkalume.gifapp.ui.screen.favorite.logic.FavoriteViewModel 7 | import com.yvkalume.gifapp.ui.screen.home.logic.HomeViewModel 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.hilt.InstallIn 11 | import dagger.multibindings.IntoMap 12 | 13 | @Module 14 | @InstallIn(MavericksViewModelComponent::class) 15 | interface ViewModelModule { 16 | 17 | @Binds 18 | @IntoMap 19 | @ViewModelKey(HomeViewModel::class) 20 | fun homeViewModel(factory: HomeViewModel.Factory): AssistedViewModelFactory<*, *> 21 | 22 | @Binds 23 | @IntoMap 24 | @ViewModelKey(FavoriteViewModel::class) 25 | fun favoriteViewModel(factory: FavoriteViewModel.Factory): AssistedViewModelFactory<*, *> 26 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/datasource/GifLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.datasource 2 | 3 | import androidx.paging.PagingSource 4 | import com.yvkalume.gifapp.data.model.room.GifEntity 5 | import com.yvkalume.gifapp.data.room.dao.GifDao 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GifLocalDataSource @Inject constructor(private val gifDao: GifDao) { 10 | fun getAll(): Flow> = gifDao.getAll() 11 | 12 | fun getAllPaginated(): PagingSource = gifDao.getAllPaginated() 13 | 14 | fun findById(id: String): GifEntity = gifDao.findById(id) 15 | 16 | suspend fun insertAll(gif: Array) = gifDao.insert(*gif) 17 | 18 | suspend fun insert(sticker: GifEntity) = gifDao.insert(sticker) 19 | 20 | fun update(sticker: GifEntity) = gifDao.update(sticker) 21 | 22 | fun getFavorites(): Flow> = gifDao.getFavorites() 23 | 24 | fun getFavoritesPaginated(): PagingSource = gifDao.getFavoritesPaginated() 25 | } -------------------------------------------------------------------------------- /data/src/test/java/com/yvkalume/gifapp/data/model/mapper/GifEntityMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.model.mapper 2 | 3 | import com.yvkalume.gifapp.data.model.room.GifEntity 4 | import com.yvkalume.gifapp.data.model.server.FixedHeight 5 | import com.yvkalume.gifapp.data.model.server.GifImage 6 | import com.yvkalume.gifapp.data.model.server.GiphyItem 7 | import org.junit.Assert.* 8 | 9 | import org.junit.Test 10 | 11 | class GifEntityMapperTest { 12 | 13 | val giphyItem = GiphyItem( 14 | id = "giphyitem1", 15 | title = "Giphy Item 1", 16 | images = GifImage( 17 | fixed_height = FixedHeight(url = "https://imageurl.com/image.png") 18 | ) 19 | ) 20 | 21 | val expectedGifEntity = GifEntity( 22 | id = "giphyitem1", 23 | title = "Giphy Item 1", 24 | imageUrl = "https://imageurl.com/image.png", 25 | createdAt = 100000, 26 | updatedAt = 100000 27 | ) 28 | 29 | @Test 30 | fun map() { 31 | val output = GifEntityMapper.map(giphyItem) 32 | assertEquals(expectedGifEntity.id,output.id) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/util/Destination.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.util 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.rounded.Favorite 6 | import androidx.compose.material.icons.rounded.Home 7 | import androidx.compose.ui.graphics.vector.ImageVector 8 | import androidx.navigation.NavDestination 9 | import androidx.navigation.NavDestination.Companion.hierarchy 10 | import com.yvkalume.gifapp.R 11 | 12 | enum class Destination( 13 | val route: String, 14 | @StringRes val label: Int, 15 | val icon: ImageVector, 16 | val isTopLeveDestination: Boolean = false, 17 | val topBarText: String? = null 18 | ) { 19 | Home("home", R.string.home, Icons.Rounded.Home, true, "GifApp"), 20 | Favorites( 21 | "favorites", 22 | R.string.favorites, 23 | Icons.Rounded.Favorite, 24 | true, 25 | topBarText = "Favorites" 26 | ) 27 | } 28 | 29 | fun NavDestination?.isCurrent(route: String): Boolean { 30 | return this?.hierarchy?.any { it.route == route } == true 31 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.di 2 | 3 | import com.yvkalume.gifapp.data.datasource.GifLocalDataSource 4 | import com.yvkalume.gifapp.data.datasource.GifRemoteDataSource 5 | import com.yvkalume.gifapp.data.repository.GifRepositoryImpl 6 | import com.yvkalume.gifapp.data.util.IoDispatcher 7 | import com.yvkalume.gifapp.domain.repository.GifRepository 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import kotlinx.coroutines.CoroutineDispatcher 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object RepositoryModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideGifRepository( 22 | remoteDataSource: GifRemoteDataSource, 23 | localDataSource: GifLocalDataSource, 24 | @IoDispatcher coroutineDispatcher: CoroutineDispatcher 25 | ): GifRepository { 26 | return GifRepositoryImpl( 27 | remoteDataSource, localDataSource, coroutineDispatcher 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/di/RoomModule.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.yvkalume.gifapp.data.room.AppDatabase 6 | import com.yvkalume.gifapp.data.room.dao.GifDao 7 | import com.yvkalume.gifapp.data.room.migrations.MIGRATION_1_2 8 | import com.yvkalume.gifapp.data.room.migrations.MIGRATION_2_3 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object RoomModule { 19 | 20 | @Singleton 21 | @Provides 22 | fun provideDatabase(@ApplicationContext context: Context): AppDatabase { 23 | return Room.databaseBuilder( 24 | context, 25 | AppDatabase::class.java, "database-name" 26 | ).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build() 27 | } 28 | 29 | @Provides 30 | fun provideGifDao(db: AppDatabase): GifDao { 31 | return db.gifDao() 32 | } 33 | } -------------------------------------------------------------------------------- /domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.yvkalume.gifapp.domain" 8 | compileSdk = 33 9 | 10 | defaultConfig { 11 | minSdk = 24 12 | targetSdk = 33 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_11 29 | targetCompatibility = JavaVersion.VERSION_11 30 | } 31 | kotlinOptions { 32 | jvmTarget = "11" 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation(libs.core.ktx) 38 | testImplementation(libs.junit.test) 39 | 40 | implementation(libs.coroutine) 41 | testImplementation(libs.coroutine.test) 42 | 43 | api(libs.paging.runtime) 44 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/room/dao/GifDao.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.room.dao 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import androidx.room.Update 9 | import com.yvkalume.gifapp.data.model.room.GifEntity 10 | import kotlinx.coroutines.flow.Flow 11 | 12 | @Dao 13 | interface GifDao { 14 | 15 | @Query("SELECT * FROM gifs ORDER BY createdAt DESC") 16 | fun getAll(): Flow> 17 | 18 | @Query("SELECT * FROM gifs ORDER BY createdAt DESC") 19 | fun getAllPaginated(): PagingSource 20 | 21 | @Query("SELECT * FROM gifs WHERE id=:id LIMIT 1") 22 | fun findById(id: String): GifEntity 23 | 24 | @Insert(onConflict = OnConflictStrategy.IGNORE) 25 | suspend fun insert(vararg gif: GifEntity) 26 | 27 | @Update 28 | fun update(gif: GifEntity) 29 | 30 | @Query("SELECT * FROM gifs WHERE isFavorite = 1 ORDER BY updatedAt DESC") 31 | fun getFavorites(): Flow> 32 | 33 | @Query("SELECT * FROM gifs WHERE isFavorite = 1 ORDER BY createdAt DESC") 34 | fun getFavoritesPaginated(): PagingSource 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Surface 9 | import androidx.compose.ui.Modifier 10 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 11 | import com.yvkalume.gifapp.ui.screen.root.RootScreen 12 | import com.yvkalume.gifapp.ui.theme.GifAppTheme 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | @AndroidEntryPoint 16 | class MainActivity : ComponentActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | installSplashScreen() 19 | super.onCreate(savedInstanceState) 20 | setContent { 21 | GifAppTheme { 22 | // A surface container using the 'background' color from the theme 23 | Surface( 24 | modifier = Modifier.fillMaxSize(), 25 | color = MaterialTheme.colorScheme.background 26 | ) { 27 | RootScreen() 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/app/GifAppState.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.app 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.remember 6 | import androidx.navigation.NavDestination 7 | import androidx.navigation.NavHostController 8 | import androidx.navigation.compose.currentBackStackEntryAsState 9 | import com.yvkalume.gifapp.ui.util.Destination 10 | import com.yvkalume.gifapp.ui.util.Destination.Favorites 11 | import com.yvkalume.gifapp.ui.util.Destination.Home 12 | 13 | @Composable 14 | fun rememberAppState(navController: NavHostController) = remember { 15 | GifAppState(navController) 16 | } 17 | 18 | @Stable 19 | class GifAppState(private val navController: NavHostController) { 20 | 21 | val currentDestination: NavDestination? 22 | @Composable get() = navController 23 | .currentBackStackEntryAsState().value?.destination 24 | 25 | val currentTopAppBarTitle: String? 26 | @Composable get() = when (currentDestination?.route) { 27 | Home.route -> Home.topBarText 28 | Favorites.route -> Favorites.topBarText 29 | else -> null 30 | } 31 | 32 | val topLevelDestination = Destination.values().filter { it.isTopLeveDestination } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/LikeIcon.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import androidx.compose.animation.core.animateFloatAsState 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.material.IconButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.ui.Modifier 9 | import com.airbnb.lottie.compose.LottieAnimation 10 | import com.airbnb.lottie.compose.LottieCompositionSpec 11 | import com.airbnb.lottie.compose.rememberLottieComposition 12 | import com.yvkalume.gifapp.R 13 | 14 | @Composable 15 | fun LikeIcon( 16 | modifier: Modifier = Modifier, 17 | isChecked: Boolean, 18 | onClick: () -> Unit 19 | ) { 20 | val composition by rememberLottieComposition( 21 | LottieCompositionSpec.RawRes(R.raw.favorite_icon) 22 | ) 23 | val progress by animateFloatAsState( 24 | targetValue = if (isChecked) 10f else 0f, 25 | animationSpec = tween(3500), 26 | label = "icon animation progress" 27 | ) 28 | 29 | IconButton(onClick = onClick) { 30 | LottieAnimation( 31 | composition = composition, 32 | progress = { if (isChecked) progress else 0f }, 33 | modifier = modifier 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/EmptyView.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.tooling.preview.Preview 7 | import com.airbnb.lottie.compose.LottieAnimation 8 | import com.airbnb.lottie.compose.LottieCompositionSpec 9 | import com.airbnb.lottie.compose.LottieConstants 10 | import com.airbnb.lottie.compose.animateLottieCompositionAsState 11 | import com.airbnb.lottie.compose.rememberLottieComposition 12 | import com.yvkalume.gifapp.R 13 | import com.yvkalume.gifapp.ui.theme.GifAppTheme 14 | 15 | @Composable 16 | fun EmptyView( 17 | modifier: Modifier = Modifier 18 | ) { 19 | val composition by rememberLottieComposition( 20 | LottieCompositionSpec.RawRes(R.raw.empty_icon) 21 | ) 22 | 23 | val progress by animateLottieCompositionAsState( 24 | composition, 25 | isPlaying = true, 26 | iterations = LottieConstants.IterateForever, 27 | ) 28 | 29 | 30 | LottieAnimation( 31 | composition = composition, 32 | progress = { progress }, 33 | modifier = modifier 34 | ) 35 | } 36 | 37 | @Preview 38 | @Composable 39 | fun EmptyViewPreview() { 40 | GifAppTheme { 41 | EmptyView() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/LoadingView.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.tooling.preview.Preview 7 | import com.airbnb.lottie.compose.LottieAnimation 8 | import com.airbnb.lottie.compose.LottieCompositionSpec 9 | import com.airbnb.lottie.compose.LottieConstants 10 | import com.airbnb.lottie.compose.animateLottieCompositionAsState 11 | import com.airbnb.lottie.compose.rememberLottieComposition 12 | import com.yvkalume.gifapp.R 13 | import com.yvkalume.gifapp.ui.theme.GifAppTheme 14 | 15 | @Composable 16 | fun LoadingView( 17 | modifier: Modifier = Modifier 18 | ) { 19 | val composition by rememberLottieComposition( 20 | LottieCompositionSpec.RawRes(R.raw.loading_icon) 21 | ) 22 | 23 | val progress by animateLottieCompositionAsState( 24 | composition, 25 | isPlaying = true, 26 | iterations = LottieConstants.IterateForever, 27 | ) 28 | 29 | 30 | LottieAnimation( 31 | composition = composition, 32 | progress = { progress }, 33 | modifier = modifier 34 | ) 35 | } 36 | 37 | @Preview 38 | @Composable 39 | fun LoadingViewPreview() { 40 | GifAppTheme { 41 | LoadingView() 42 | } 43 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/GifListView.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.wrapContentHeight 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.foundation.lazy.rememberLazyListState 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import com.yvkalume.gifapp.domain.entity.Gif 13 | 14 | @OptIn(ExperimentalFoundationApi::class) 15 | @Composable 16 | fun GifListView( 17 | modifier: Modifier = Modifier, 18 | gifs: List, 19 | onFavoriteClick: (Gif) -> Unit 20 | ) { 21 | val listState = rememberLazyListState() 22 | LazyColumn( 23 | state = listState, 24 | modifier = modifier.fillMaxSize(), 25 | content = { 26 | items(items = gifs, key = { it.id }) { gif -> 27 | GifItem( 28 | gif = gif, 29 | onFavoriteClick = onFavoriteClick, 30 | modifier = Modifier 31 | .wrapContentHeight() 32 | .animateItemPlacement( 33 | animationSpec = tween(2000) 34 | ) 35 | ) 36 | } 37 | } 38 | ) 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/util/Modifier.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.util 2 | 3 | import androidx.compose.animation.core.LinearOutSlowInEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.animateFloat 6 | import androidx.compose.animation.core.infiniteRepeatable 7 | import androidx.compose.animation.core.rememberInfiniteTransition 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.foundation.background 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.composed 13 | import androidx.compose.ui.geometry.Offset 14 | import androidx.compose.ui.graphics.Brush 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.graphics.TileMode 17 | 18 | fun Modifier.shimmer(): Modifier = composed { 19 | val transition = rememberInfiniteTransition() 20 | val translateAnimation by transition.animateFloat( 21 | initialValue = 0f, 22 | targetValue = 400f, 23 | animationSpec = infiniteRepeatable( 24 | tween(durationMillis = 1500, easing = LinearOutSlowInEasing), 25 | RepeatMode.Restart 26 | ), 27 | ) 28 | val shimmerColors = listOf( 29 | Color.LightGray.copy(alpha = 0.9f), 30 | Color.LightGray.copy(alpha = 0.4f), 31 | ) 32 | val brush = Brush.linearGradient( 33 | colors = shimmerColors, 34 | start = Offset(translateAnimation, translateAnimation), 35 | end = Offset(translateAnimation + 100f, translateAnimation + 100f), 36 | tileMode = TileMode.Mirror, 37 | ) 38 | return@composed this.then(background(brush)) 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/home/logic/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.home.logic 2 | 3 | import com.airbnb.mvrx.MavericksViewModel 4 | import com.airbnb.mvrx.MavericksViewModelFactory 5 | import com.yvkalume.gifapp.di.mavericks.AssistedViewModelFactory 6 | import com.yvkalume.gifapp.di.mavericks.hiltMavericksViewModelFactory 7 | import com.yvkalume.gifapp.domain.entity.Gif 8 | import com.yvkalume.gifapp.domain.repository.GifRepository 9 | import dagger.assisted.Assisted 10 | import dagger.assisted.AssistedFactory 11 | import dagger.assisted.AssistedInject 12 | import kotlinx.coroutines.launch 13 | 14 | class HomeViewModel @AssistedInject constructor( 15 | @Assisted initialState: HomeUiState, 16 | private val gifRepository: GifRepository, 17 | ) : MavericksViewModel(initialState) { 18 | 19 | init { 20 | getData() 21 | } 22 | 23 | private fun getData() { 24 | viewModelScope.launch { 25 | gifRepository.refresh() 26 | gifRepository.getAllTrending().execute { 27 | copy(gifs = it) 28 | } 29 | } 30 | } 31 | 32 | fun toggleFavorite(gif: Gif) { 33 | viewModelScope.launch { 34 | val updatedGif = gif.copy(isFavorite = !gif.isFavorite) 35 | gifRepository.update(updatedGif) 36 | } 37 | } 38 | 39 | @AssistedFactory 40 | interface Factory : AssistedViewModelFactory { 41 | override fun create(state: HomeUiState): HomeViewModel 42 | } 43 | 44 | companion object : 45 | MavericksViewModelFactory by hiltMavericksViewModelFactory() 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/favorite/logic/FavoriteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.favorite.logic 2 | 3 | import com.airbnb.mvrx.MavericksViewModel 4 | import com.airbnb.mvrx.MavericksViewModelFactory 5 | import com.yvkalume.gifapp.di.mavericks.AssistedViewModelFactory 6 | import com.yvkalume.gifapp.di.mavericks.hiltMavericksViewModelFactory 7 | import com.yvkalume.gifapp.domain.entity.Gif 8 | import com.yvkalume.gifapp.domain.repository.GifRepository 9 | import dagger.assisted.Assisted 10 | import dagger.assisted.AssistedFactory 11 | import dagger.assisted.AssistedInject 12 | import kotlinx.coroutines.launch 13 | 14 | class FavoriteViewModel @AssistedInject constructor( 15 | @Assisted initialState: FavoriteUiState, 16 | private val gifRepository: GifRepository, 17 | ) : MavericksViewModel(initialState) { 18 | 19 | init { 20 | getData() 21 | } 22 | 23 | private fun getData() { 24 | viewModelScope.launch { 25 | gifRepository.getFavorites().execute { 26 | copy(gifs = it) 27 | } 28 | } 29 | } 30 | 31 | fun removeFavorite(gif: Gif) { 32 | viewModelScope.launch { 33 | val updatedGif = gif.copy(isFavorite = !gif.isFavorite) 34 | gifRepository.update(updatedGif) 35 | } 36 | } 37 | 38 | @AssistedFactory 39 | interface Factory : AssistedViewModelFactory { 40 | override fun create(state: FavoriteUiState): FavoriteViewModel 41 | } 42 | 43 | companion object : 44 | MavericksViewModelFactory by hiltMavericksViewModelFactory() 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.util 2 | 3 | import android.app.DownloadManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.Bitmap 7 | import android.net.Uri 8 | import android.os.Environment 9 | import android.provider.MediaStore 10 | import com.google.modernstorage.permissions.StoragePermissions 11 | import com.google.modernstorage.permissions.StoragePermissions.Action 12 | import com.google.modernstorage.permissions.StoragePermissions.CreatedBy 13 | import com.google.modernstorage.permissions.StoragePermissions.FileType 14 | 15 | fun Context.downloadFile(link: String, fileName: String): Long { 16 | 17 | StoragePermissions.getPermissions( 18 | action = Action.READ_AND_WRITE, 19 | types = listOf(FileType.Image), 20 | createdBy = CreatedBy.AllApps 21 | ) 22 | 23 | val request = DownloadManager.Request(Uri.parse(link)) 24 | val params = DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE 25 | request.setAllowedNetworkTypes(params) 26 | .setTitle("Gif app") 27 | .setDescription("Downloading a gif") 28 | .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) 29 | .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName) 30 | val manager = this.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager 31 | 32 | return manager.enqueue(request) 33 | } 34 | 35 | 36 | fun Bitmap?.saveAndShare(context: Context, title: String) { 37 | if (this == null) { 38 | return 39 | } 40 | val bitmapPath: String = MediaStore.Images.Media.insertImage( 41 | context.contentResolver, 42 | this, 43 | title, 44 | null 45 | ) 46 | val bitmapUri = Uri.parse(bitmapPath) 47 | val shareIntent = Intent(Intent.ACTION_SEND) 48 | shareIntent.type = "image/gif"; 49 | shareIntent.putExtra(Intent.EXTRA_STREAM, bitmapUri); 50 | context.startActivity(Intent.createChooser(shareIntent, title)) 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import com.yvkalume.gifapp.R 10 | 11 | private val fonts = FontFamily( 12 | Font(R.font.roboto_regular), 13 | Font(resId = R.font.roboto_light, weight = FontWeight.Light), 14 | Font(resId = R.font.roboto_medium, weight = FontWeight.Medium), 15 | Font(resId = R.font.roboto_regular, weight = FontWeight.Normal), 16 | Font(resId = R.font.roboto_thin, weight = FontWeight.Thin), 17 | Font(resId = R.font.roboto_bold, weight = FontWeight.Bold), 18 | ) 19 | 20 | // Set of Material typography styles to start with 21 | val Typography = Typography( 22 | titleLarge = TextStyle( 23 | fontFamily = fonts, 24 | fontWeight = FontWeight.SemiBold, 25 | fontSize = 24.sp, 26 | ), 27 | 28 | titleMedium = TextStyle( 29 | fontFamily = fonts, 30 | fontWeight = FontWeight.Bold, 31 | fontSize = 20.sp, 32 | ), 33 | 34 | titleSmall = TextStyle( 35 | fontFamily = fonts, 36 | fontWeight = FontWeight.Normal, 37 | fontSize = 18.sp, 38 | ), 39 | 40 | headlineLarge = TextStyle( 41 | fontFamily = fonts, 42 | fontWeight = FontWeight.Normal, 43 | fontSize = 16.sp, 44 | ), 45 | 46 | bodyMedium = TextStyle( 47 | fontFamily = fonts, 48 | fontWeight = FontWeight.Normal, 49 | fontSize = 16.sp, 50 | ), 51 | 52 | bodySmall = TextStyle( 53 | fontFamily = fonts, 54 | fontWeight = FontWeight.Normal, 55 | fontSize = 12.sp, 56 | ), 57 | labelLarge = TextStyle( 58 | fontFamily = fonts, 59 | fontWeight = FontWeight.W500, 60 | fontSize = 14.sp, 61 | ), 62 | ) -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/di/DataSourceModule.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.di 2 | 3 | import android.content.Context 4 | import com.chuckerteam.chucker.api.ChuckerCollector 5 | import com.chuckerteam.chucker.api.ChuckerInterceptor 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import io.ktor.client.HttpClient 12 | import io.ktor.client.engine.HttpClientEngine 13 | import io.ktor.client.engine.okhttp.OkHttp 14 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 15 | import io.ktor.serialization.kotlinx.json.json 16 | import kotlinx.serialization.json.Json 17 | import javax.inject.Singleton 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object DataSourceModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideHttpClientEngine(chuckerInterceptor: ChuckerInterceptor): HttpClientEngine { 26 | return OkHttp.create { 27 | addInterceptor(chuckerInterceptor) 28 | } 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideKtor(httpClientEngine: HttpClientEngine): HttpClient { 34 | return HttpClient(httpClientEngine) { 35 | expectSuccess = true 36 | install(ContentNegotiation) { 37 | json(Json { 38 | isLenient = true 39 | ignoreUnknownKeys = true 40 | prettyPrint = true 41 | }) 42 | } 43 | } 44 | } 45 | 46 | @Provides 47 | @Singleton 48 | fun provideChuckerInterceptor(@ApplicationContext context: Context): ChuckerInterceptor { 49 | return ChuckerInterceptor.Builder(context) 50 | .collector(ChuckerCollector(context)) 51 | .maxContentLength(250000L) 52 | .redactHeaders(emptySet()) 53 | .alwaysReadResponseBody(false) 54 | .build() 55 | } 56 | } -------------------------------------------------------------------------------- /data/src/main/java/com/yvkalume/gifapp/data/repository/GifRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.data.repository 2 | 3 | import com.yvkalume.gifapp.data.datasource.GifLocalDataSource 4 | import com.yvkalume.gifapp.data.datasource.GifRemoteDataSource 5 | import com.yvkalume.gifapp.data.model.mapper.GifEntityMapper 6 | import com.yvkalume.gifapp.data.model.mapper.GifMapper 7 | import com.yvkalume.gifapp.data.model.room.toEntity 8 | import com.yvkalume.gifapp.domain.entity.Gif 9 | import com.yvkalume.gifapp.domain.repository.GifRepository 10 | import kotlinx.coroutines.CoroutineDispatcher 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.withContext 14 | import timber.log.Timber 15 | import javax.inject.Inject 16 | 17 | class GifRepositoryImpl @Inject constructor( 18 | private val remoteDataSource: GifRemoteDataSource, 19 | private val localDataSource: GifLocalDataSource, 20 | private val coroutineDispatcher: CoroutineDispatcher 21 | ) : GifRepository { 22 | 23 | override fun getAllTrending(): Flow> { 24 | return localDataSource.getAll().map { GifMapper.mapList(it) } 25 | } 26 | 27 | override suspend fun update(gif: Gif) { 28 | val updatedGif = gif.copy(updatedAt = System.currentTimeMillis()) 29 | withContext(coroutineDispatcher) { 30 | localDataSource.update(updatedGif.toEntity()) 31 | } 32 | } 33 | 34 | override fun getFavorites(): Flow> { 35 | return localDataSource.getFavorites().map { GifMapper.mapList(it) } 36 | } 37 | 38 | override suspend fun refresh() { 39 | withContext(coroutineDispatcher) { 40 | try { 41 | val response = remoteDataSource.getAllTrending() 42 | if (response.meta.status == 200) { 43 | val gifEntities = GifEntityMapper.mapList(response.data) 44 | localDataSource.insertAll(gifEntities.toTypedArray()) 45 | } 46 | } catch (t: Throwable) { 47 | Timber.e(t.message.toString()) 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/favorite/FavoriteScreen.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.favorite 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.ui.Alignment 8 | import com.airbnb.mvrx.Async 9 | import com.airbnb.mvrx.Fail 10 | import com.airbnb.mvrx.Loading 11 | import com.airbnb.mvrx.Success 12 | import com.airbnb.mvrx.Uninitialized 13 | import com.airbnb.mvrx.compose.collectAsState 14 | import com.airbnb.mvrx.compose.mavericksViewModel 15 | import com.yvkalume.gifapp.domain.entity.Gif 16 | import com.yvkalume.gifapp.ui.components.EmptyView 17 | import com.yvkalume.gifapp.ui.components.GifListView 18 | import com.yvkalume.gifapp.ui.components.LoadingView 19 | import com.yvkalume.gifapp.ui.screen.favorite.logic.FavoriteUiState 20 | import com.yvkalume.gifapp.ui.screen.favorite.logic.FavoriteViewModel 21 | 22 | @Composable 23 | fun FavoriteRoute(homeViewModel: FavoriteViewModel = mavericksViewModel()) { 24 | val gifsState by homeViewModel.collectAsState(FavoriteUiState::gifs) 25 | FavoriteScreen(gifsState = gifsState, onFavoriteClick = { homeViewModel.removeFavorite(it) }) 26 | } 27 | 28 | @OptIn(ExperimentalMaterial3Api::class) 29 | @Composable 30 | fun FavoriteScreen( 31 | gifsState: Async>, 32 | onFavoriteClick: (Gif) -> Unit 33 | ) { 34 | 35 | Box(contentAlignment = Alignment.Center) { 36 | when (gifsState) { 37 | is Fail -> { 38 | EmptyView() 39 | } 40 | 41 | is Loading -> { 42 | LoadingView() 43 | } 44 | 45 | is Success -> { 46 | val gifs = gifsState.invoke() 47 | if (gifs.isNotEmpty()) { 48 | GifListView( 49 | gifs = gifs, 50 | onFavoriteClick = onFavoriteClick 51 | ) 52 | } else { 53 | EmptyView() 54 | } 55 | } 56 | 57 | Uninitialized -> {} 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | 19 | private val DarkColorScheme = darkColorScheme( 20 | primary = Green200, 21 | secondary = LightGreen, 22 | tertiary = Blue200, 23 | onSurface = Gray100 24 | ) 25 | 26 | private val LightColorScheme = lightColorScheme( 27 | primary = Green200, 28 | secondary = LightGreen, 29 | tertiary = Blue200, 30 | ) 31 | 32 | @Composable 33 | fun GifAppTheme( 34 | darkTheme: Boolean = isSystemInDarkTheme(), 35 | dynamicColor: Boolean = true, 36 | content: @Composable () -> Unit 37 | ) { 38 | 39 | val colorScheme = when { 40 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 41 | val context = LocalContext.current 42 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 43 | } 44 | 45 | darkTheme -> DarkColorScheme 46 | else -> LightColorScheme 47 | } 48 | val view = LocalView.current 49 | if (!view.isInEditMode) { 50 | SideEffect { 51 | val window = (view.context as Activity).window 52 | window.statusBarColor = colorScheme.primary.toArgb() 53 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 54 | } 55 | } 56 | 57 | MaterialTheme( 58 | colorScheme = colorScheme, 59 | typography = Typography, 60 | shapes = Shapes, 61 | content = content 62 | ) 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/CustomImageView.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.defaultMinSize 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.layout.ContentScale 15 | import androidx.compose.ui.platform.LocalContext 16 | import androidx.compose.ui.unit.dp 17 | import coil.ImageLoader 18 | import coil.compose.SubcomposeAsyncImage 19 | import coil.decode.GifDecoder 20 | import coil.decode.ImageDecoderDecoder 21 | import com.yvkalume.gifapp.ui.util.shimmer 22 | import kotlinx.coroutines.Dispatchers 23 | import kotlinx.coroutines.ExperimentalCoroutinesApi 24 | 25 | @OptIn(ExperimentalCoroutinesApi::class) 26 | @Composable 27 | fun CustomImageView( 28 | modifier: Modifier = Modifier, 29 | imageUrl: String, 30 | contentScale: ContentScale = ContentScale.Crop, 31 | ) { 32 | val context = LocalContext.current 33 | val dispatcher = Dispatchers.IO.limitedParallelism(5) 34 | 35 | SubcomposeAsyncImage( 36 | modifier = Modifier 37 | .fillMaxWidth() 38 | .defaultMinSize(minHeight = 300.dp) 39 | .padding(16.dp) 40 | .clip(RoundedCornerShape(24.dp)) 41 | .then(modifier), 42 | model = imageUrl, 43 | loading = { 44 | Box( 45 | modifier = Modifier 46 | .fillMaxSize() 47 | .shimmer() 48 | ) 49 | }, 50 | imageLoader = ImageLoader.Builder(context) 51 | .dispatcher(dispatcher) 52 | .components { 53 | if (Build.VERSION.SDK_INT >= 28) { 54 | add(ImageDecoderDecoder.Factory()) 55 | } else { 56 | add(GifDecoder.Factory()) 57 | } 58 | } 59 | .respectCacheHeaders(false) 60 | .build(), 61 | contentDescription = null, 62 | contentScale = contentScale, 63 | alignment = Alignment.Center, 64 | ) 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.home 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.tooling.preview.Preview 9 | import com.airbnb.mvrx.Async 10 | import com.airbnb.mvrx.Fail 11 | import com.airbnb.mvrx.Loading 12 | import com.airbnb.mvrx.Success 13 | import com.airbnb.mvrx.Uninitialized 14 | import com.airbnb.mvrx.compose.collectAsState 15 | import com.airbnb.mvrx.compose.mavericksViewModel 16 | import com.yvkalume.gifapp.domain.entity.Gif 17 | import com.yvkalume.gifapp.ui.components.EmptyView 18 | import com.yvkalume.gifapp.ui.components.GifListView 19 | import com.yvkalume.gifapp.ui.components.LoadingView 20 | import com.yvkalume.gifapp.ui.screen.home.logic.HomeUiState 21 | import com.yvkalume.gifapp.ui.screen.home.logic.HomeViewModel 22 | import com.yvkalume.gifapp.ui.theme.GifAppTheme 23 | 24 | @Composable 25 | fun HomeRoute(homeViewModel: HomeViewModel = mavericksViewModel()) { 26 | val gifsState by homeViewModel.collectAsState(HomeUiState::gifs) 27 | HomeScreen( 28 | gifsState = gifsState, 29 | onFavoriteClick = { homeViewModel.toggleFavorite(it) } 30 | ) 31 | } 32 | 33 | @Composable 34 | fun HomeScreen( 35 | gifsState: Async>, 36 | onFavoriteClick: (Gif) -> Unit 37 | ) { 38 | Box(contentAlignment = Alignment.Center) { 39 | when (gifsState) { 40 | is Fail -> { 41 | EmptyView() 42 | } 43 | 44 | is Loading -> { 45 | LoadingView() 46 | } 47 | 48 | is Success -> { 49 | val gifs = gifsState.invoke() 50 | if (gifs.isNotEmpty()) { 51 | GifListView( 52 | gifs = gifs, 53 | onFavoriteClick = onFavoriteClick 54 | ) 55 | } else { 56 | EmptyView() 57 | } 58 | } 59 | 60 | Uninitialized -> {} 61 | } 62 | } 63 | } 64 | 65 | @Preview 66 | @Composable 67 | fun HomeScreenPreview() { 68 | GifAppTheme { 69 | HomeScreen( 70 | gifsState = Success(emptyList()), 71 | onFavoriteClick = {} 72 | ) 73 | } 74 | } -------------------------------------------------------------------------------- /data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | id("dagger.hilt.android.plugin") 5 | id("com.google.devtools.ksp") 6 | kotlin("kapt") 7 | kotlin("plugin.serialization") version "1.8.10" 8 | id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") 9 | 10 | } 11 | 12 | android { 13 | namespace = "com.yvkalume.gifapp.data" 14 | compileSdk = 33 15 | 16 | defaultConfig { 17 | minSdk = 24 18 | targetSdk = 33 19 | 20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 21 | consumerProguardFiles("consumer-rules.pro") 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_11 35 | targetCompatibility = JavaVersion.VERSION_11 36 | } 37 | kotlinOptions { 38 | jvmTarget = "11" 39 | } 40 | } 41 | 42 | dependencies { 43 | api(project(":domain")) 44 | 45 | implementation(libs.core.ktx) 46 | implementation(libs.lifecycle.ktx) 47 | 48 | testImplementation(libs.junit.test) 49 | androidTestImplementation(libs.junit.androidTest) 50 | androidTestImplementation(libs.espresso.androidTest) 51 | testImplementation(libs.mockk) 52 | 53 | implementation(libs.ktor.core) 54 | implementation(libs.ktor.android) 55 | implementation(libs.ktor.serialization) 56 | implementation(libs.ktor.contentnegotiation) 57 | implementation(libs.ktor.okhttp) 58 | 59 | implementation(libs.kotlin.serialization) 60 | 61 | implementation(libs.hilt.android) 62 | kapt(libs.hilt.compiler) 63 | testImplementation(libs.hilt.test) 64 | 65 | implementation(libs.coroutine) 66 | testImplementation(libs.coroutine.test) 67 | 68 | implementation(libs.room.runtime) 69 | implementation(libs.room.paging) 70 | implementation(libs.room.ktx) 71 | ksp(libs.room.ksp) 72 | testImplementation(libs.room.test) 73 | 74 | testImplementation(libs.faker) 75 | 76 | debugImplementation(libs.chucker.debug) 77 | releaseImplementation(libs.chucker.release) 78 | 79 | implementation(libs.kotlinx.serialization.json) 80 | 81 | api(libs.timber) 82 | } 83 | 84 | secrets { 85 | // Change the properties file from the default "local.properties" in your root project 86 | // to another properties file in your root project. 87 | propertiesFileName = "secrets.properties" 88 | 89 | // A properties file containing default secret values. This file can be checked in version 90 | // control. 91 | defaultPropertiesFileName = "secrets.sample.properties" 92 | 93 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | id("dagger.hilt.android.plugin") 5 | kotlin("kapt") 6 | } 7 | 8 | android { 9 | namespace = "com.yvkalume.gifapp" 10 | compileSdk = 33 11 | 12 | signingConfigs { 13 | create("release") { 14 | storeFile = file("/Users/yves-kalume/Documents/developerkey.jks") 15 | storePassword = " " 16 | keyAlias = " " 17 | keyPassword = " " 18 | } 19 | } 20 | 21 | defaultConfig { 22 | applicationId = "com.yvkalume.gifapp" 23 | minSdk = 24 24 | targetSdk = 33 25 | versionCode = 5 26 | versionName = "2.1" 27 | 28 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 29 | vectorDrawables { 30 | useSupportLibrary = true 31 | } 32 | } 33 | 34 | buildTypes { 35 | release { 36 | isMinifyEnabled = true 37 | isShrinkResources = true 38 | proguardFiles( 39 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 40 | ) 41 | } 42 | } 43 | compileOptions { 44 | sourceCompatibility = JavaVersion.VERSION_11 45 | targetCompatibility = JavaVersion.VERSION_11 46 | } 47 | kotlinOptions { 48 | jvmTarget = "11" 49 | } 50 | buildFeatures { 51 | compose = true 52 | } 53 | composeOptions { 54 | kotlinCompilerExtensionVersion = "1.4.2" 55 | } 56 | packagingOptions { 57 | resources { 58 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 59 | } 60 | } 61 | } 62 | 63 | dependencies { 64 | 65 | implementation(project(":data")) 66 | 67 | implementation(libs.core.ktx) 68 | implementation(libs.lifecycle.ktx) 69 | implementation(libs.bundles.compose) 70 | implementation(libs.compose.material) 71 | implementation(libs.compose.material3) 72 | 73 | implementation(libs.coil.compose) 74 | implementation(libs.coil.gif) 75 | 76 | implementation(libs.hilt.android) 77 | implementation(libs.hilt.navigation.compose) 78 | kapt(libs.hilt.compiler) 79 | testImplementation(libs.hilt.test) 80 | 81 | implementation(libs.pager) 82 | implementation(libs.pager.indicators) 83 | 84 | implementation(libs.lottie.compose) 85 | 86 | debugImplementation(libs.bundles.compose.debug) 87 | 88 | testImplementation(libs.junit.test) 89 | androidTestImplementation(libs.junit.androidTest) 90 | androidTestImplementation(libs.espresso.androidTest) 91 | testImplementation(libs.mockk) 92 | 93 | implementation(libs.coroutine) 94 | testImplementation(libs.coroutine.test) 95 | 96 | implementation(libs.mavericks) 97 | implementation(libs.mavericks.compose) 98 | testImplementation(libs.mavericks.testing) 99 | 100 | 101 | implementation(libs.paging.compose) 102 | 103 | implementation(libs.accompanist.permision) 104 | 105 | implementation(libs.modernStorage.permission) 106 | 107 | testImplementation(libs.turbine) 108 | } 109 | 110 | kapt { 111 | correctErrorTypes = true 112 | } -------------------------------------------------------------------------------- /app/src/test/java/com/yvkalume/gifapp/ui/screen/home/logic/HomeViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.home.logic 2 | 3 | import app.cash.turbine.test 4 | import com.airbnb.mvrx.Async 5 | import com.airbnb.mvrx.Success 6 | import com.airbnb.mvrx.Uninitialized 7 | import com.airbnb.mvrx.test.MavericksTestRule 8 | import com.yvkalume.gifapp.domain.entity.Gif 9 | import com.yvkalume.gifapp.domain.repository.GifRepository 10 | import io.mockk.coEvery 11 | import io.mockk.coVerify 12 | import io.mockk.every 13 | import io.mockk.impl.annotations.RelaxedMockK 14 | import io.mockk.junit4.MockKRule 15 | import io.mockk.just 16 | import io.mockk.mockk 17 | import io.mockk.runs 18 | import io.mockk.spyk 19 | import kotlinx.coroutines.ExperimentalCoroutinesApi 20 | import kotlinx.coroutines.flow.flowOf 21 | import kotlinx.coroutines.test.runTest 22 | import org.junit.Assert.assertEquals 23 | import org.junit.Before 24 | import org.junit.Rule 25 | import org.junit.Test 26 | import java.util.Date 27 | 28 | @ExperimentalCoroutinesApi 29 | class HomeViewModelTest { 30 | 31 | @get:Rule 32 | val mavericksRule = MavericksTestRule() 33 | 34 | @get:Rule 35 | val mockkRule = MockKRule(this) 36 | 37 | private lateinit var initialState: HomeUiState 38 | 39 | @RelaxedMockK 40 | private lateinit var gifRepository: GifRepository 41 | 42 | private lateinit var viewModel: HomeViewModel 43 | 44 | @Before 45 | fun setup() { 46 | initialState = HomeUiState(gifs = Uninitialized) 47 | } 48 | 49 | @Test 50 | fun `getData() should call refresh() and execute getAllTrending() with correct state`() = 51 | runTest { 52 | // Mock the response from getAllTrending() 53 | val gifList: List = mockk() 54 | val successResult: Async> = Success(gifList) 55 | 56 | coEvery { gifRepository.getAllTrending() } returns flowOf(gifList) 57 | 58 | coEvery { gifRepository.refresh() } just runs 59 | 60 | // getData() is called within the init block, when HomeViewModel is instanced 61 | viewModel = HomeViewModel(initialState, gifRepository) 62 | 63 | // Verify that the getAllTrending() function is called 64 | coVerify { gifRepository.getAllTrending() } 65 | 66 | viewModel.stateFlow.test { 67 | assertEquals(successResult, awaitItem().gifs) 68 | cancelAndIgnoreRemainingEvents() 69 | } 70 | } 71 | 72 | @Test 73 | fun `toggleFavorite() should call update() from gifRepository`() = runTest { 74 | val gif: Gif = spyk( 75 | Gif( 76 | "1", 77 | "name", 78 | "http://example.com", 79 | isFavorite = false, 80 | createdAt = System.currentTimeMillis(), 81 | updatedAt = System.currentTimeMillis() 82 | ) 83 | ) 84 | 85 | // coEvery { gifRepository.update(any()) } just runs 86 | 87 | viewModel = HomeViewModel(initialState, gifRepository) 88 | 89 | // Call the function under test 90 | viewModel.toggleFavorite(gif) 91 | 92 | // Verify that the update() function is called with the updated gif 93 | coVerify { gifRepository.update(any()) } 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/di/mavericks/HiltMavericksViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.di.mavericks 2 | 3 | import com.airbnb.mvrx.MavericksState 4 | import com.airbnb.mvrx.MavericksViewModel 5 | import com.airbnb.mvrx.MavericksViewModelFactory 6 | import com.airbnb.mvrx.ViewModelContext 7 | import dagger.hilt.DefineComponent 8 | import dagger.hilt.EntryPoint 9 | import dagger.hilt.EntryPoints 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | 13 | /** 14 | * To connect Mavericks ViewModel creation with Hilt's dependency injection, add the following Factory and companion object to your MavericksViewModel. 15 | * 16 | * Example: 17 | * 18 | * class MyViewModel @AssistedInject constructor(...): MavericksViewModel(...) { 19 | * 20 | * @AssistedFactory 21 | * interface Factory : AssistedViewModelFactory { 22 | * ... 23 | * } 24 | * 25 | * companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() 26 | * } 27 | */ 28 | 29 | inline fun , S : MavericksState> hiltMavericksViewModelFactory() = 30 | HiltMavericksViewModelFactory(VM::class.java) 31 | 32 | class HiltMavericksViewModelFactory, S : MavericksState>( 33 | private val viewModelClass: Class> 34 | ) : MavericksViewModelFactory { 35 | 36 | override fun create(viewModelContext: ViewModelContext, state: S): VM { 37 | // We want to create the ViewModelComponent. In order to do that, we need to get its parent: ActivityComponent. 38 | val componentBuilder = 39 | EntryPoints.get(viewModelContext.app(), CreateMavericksViewModelComponent::class.java) 40 | .mavericksViewModelComponentBuilder() 41 | val viewModelComponent = componentBuilder.build() 42 | val viewModelFactoryMap = EntryPoints.get( 43 | viewModelComponent, 44 | HiltMavericksEntryPoint::class.java 45 | ).viewModelFactories 46 | val viewModelFactory = viewModelFactoryMap[viewModelClass] 47 | 48 | @Suppress("UNCHECKED_CAST") 49 | val castedViewModelFactory = viewModelFactory as? AssistedViewModelFactory 50 | return castedViewModelFactory?.create(state) as VM 51 | } 52 | } 53 | 54 | /** 55 | * Hilt's ViewModelComponent's parent is ActivityRetainedComponent but there is no easy way to access it. SingletonComponent should be sufficient 56 | * because the ViewModel that gets created is the only object with a reference to the created component so the lifecycle of it will 57 | * still be correct. 58 | */ 59 | @MavericksViewModelScoped 60 | @DefineComponent(parent = SingletonComponent::class) 61 | interface MavericksViewModelComponent 62 | 63 | @DefineComponent.Builder 64 | interface MavericksViewModelComponentBuilder { 65 | fun build(): MavericksViewModelComponent 66 | } 67 | 68 | @EntryPoint 69 | @InstallIn(SingletonComponent::class) 70 | interface CreateMavericksViewModelComponent { 71 | fun mavericksViewModelComponentBuilder(): MavericksViewModelComponentBuilder 72 | } 73 | 74 | @EntryPoint 75 | @InstallIn(MavericksViewModelComponent::class) 76 | interface HiltMavericksEntryPoint { 77 | val viewModelFactories: Map>, AssistedViewModelFactory<*, *>> 78 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/components/GifItem.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.components 2 | 3 | import android.widget.Toast 4 | import androidx.activity.compose.rememberLauncherForActivityResult 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.size 11 | import androidx.compose.foundation.layout.wrapContentHeight 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.rounded.Download 14 | import androidx.compose.material3.Divider 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.IconButton 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.layout.ContentScale 21 | import androidx.compose.ui.platform.LocalContext 22 | import androidx.compose.ui.unit.dp 23 | import com.google.modernstorage.permissions.RequestAccess 24 | import com.google.modernstorage.permissions.StoragePermissions.Action 25 | import com.google.modernstorage.permissions.StoragePermissions.CreatedBy 26 | import com.google.modernstorage.permissions.StoragePermissions.FileType 27 | import com.yvkalume.gifapp.domain.entity.Gif 28 | import com.yvkalume.gifapp.util.downloadFile 29 | 30 | @Composable 31 | fun GifItem(gif: Gif, modifier: Modifier = Modifier, onFavoriteClick: (Gif) -> Unit) { 32 | 33 | val context = LocalContext.current 34 | 35 | val requestAccess = rememberLauncherForActivityResult(RequestAccess()) { hasAccess -> 36 | if (hasAccess) { 37 | Toast.makeText(context, "Download started", Toast.LENGTH_LONG).show() 38 | context.downloadFile(gif.imageUrl, "${gif.title}.gif") 39 | } else { 40 | Toast.makeText(context, "Not Download permission", Toast.LENGTH_LONG).show() 41 | } 42 | } 43 | 44 | Column(modifier = Modifier.wrapContentHeight()) { 45 | CustomImageView( 46 | modifier = modifier, 47 | imageUrl = gif.imageUrl, 48 | contentScale = ContentScale.FillWidth, 49 | ) 50 | Row( 51 | modifier = Modifier 52 | .fillMaxWidth() 53 | .padding(horizontal = 16.dp), 54 | horizontalArrangement = Arrangement.SpaceBetween, 55 | verticalAlignment = Alignment.CenterVertically 56 | ) { 57 | LikeIcon( 58 | isChecked = gif.isFavorite, 59 | onClick = { onFavoriteClick(gif) }, 60 | modifier = Modifier.size(52.dp) 61 | ) 62 | 63 | IconButton( 64 | onClick = { 65 | requestAccess.launch( 66 | RequestAccess.Args( 67 | action = Action.READ_AND_WRITE, 68 | types = listOf( 69 | FileType.Image, 70 | ), 71 | createdBy = CreatedBy.Self 72 | ) 73 | ) 74 | } 75 | ) { 76 | Icon(imageVector = Icons.Rounded.Download, contentDescription = null) 77 | } 78 | } 79 | } 80 | 81 | Divider(modifier = Modifier.padding(vertical = 16.dp)) 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yvkalume/gifapp/ui/screen/root/RootScreen.kt: -------------------------------------------------------------------------------- 1 | package com.yvkalume.gifapp.ui.screen.root 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.CenterAlignedTopAppBar 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.NavigationBar 10 | import androidx.compose.material3.NavigationBarItem 11 | import androidx.compose.material3.Scaffold 12 | import androidx.compose.material3.Surface 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.unit.dp 18 | import androidx.navigation.NavGraph.Companion.findStartDestination 19 | import androidx.navigation.compose.NavHost 20 | import androidx.navigation.compose.composable 21 | import androidx.navigation.compose.rememberNavController 22 | import com.yvkalume.gifapp.app.rememberAppState 23 | import com.yvkalume.gifapp.ui.screen.favorite.FavoriteRoute 24 | import com.yvkalume.gifapp.ui.screen.home.HomeRoute 25 | import com.yvkalume.gifapp.ui.util.Destination 26 | import com.yvkalume.gifapp.ui.util.isCurrent 27 | 28 | 29 | @OptIn(ExperimentalMaterial3Api::class) 30 | @Composable 31 | fun RootScreen(modifier: Modifier = Modifier) { 32 | val navController = rememberNavController() 33 | val appState = rememberAppState(navController = navController) 34 | 35 | Scaffold( 36 | modifier = modifier, 37 | topBar = { 38 | if (appState.currentTopAppBarTitle != null) { 39 | Surface(shadowElevation = 2.dp) { 40 | CenterAlignedTopAppBar( 41 | title = { 42 | Text( 43 | text = appState.currentTopAppBarTitle.toString(), 44 | style = MaterialTheme.typography.titleMedium, 45 | ) 46 | } 47 | ) 48 | } 49 | } 50 | }, 51 | bottomBar = { 52 | NavigationBar { 53 | appState.topLevelDestination.forEach { destination -> 54 | val currentDestination = appState.currentDestination 55 | NavigationBarItem( 56 | selected = currentDestination.isCurrent(destination.route), 57 | onClick = { 58 | navController.navigate(destination.route) { 59 | popUpTo(navController.graph.findStartDestination().id) { 60 | saveState = true 61 | } 62 | launchSingleTop = true 63 | restoreState = true 64 | } 65 | }, 66 | icon = { 67 | Icon( 68 | imageVector = destination.icon, 69 | contentDescription = stringResource(id = destination.label), 70 | tint = if (currentDestination.isCurrent(destination.route)) { 71 | MaterialTheme.colorScheme.primary 72 | } else { 73 | MaterialTheme.colorScheme.onSurface 74 | } 75 | ) 76 | }, 77 | label = { 78 | Text( 79 | text = stringResource(id = destination.label), 80 | color = if (currentDestination.isCurrent(destination.route)) { 81 | MaterialTheme.colorScheme.primary 82 | } else { 83 | MaterialTheme.colorScheme.onSurface 84 | } 85 | ) 86 | } 87 | ) 88 | } 89 | } 90 | } 91 | ) { contentPadding -> 92 | NavHost( 93 | navController = navController, 94 | startDestination = Destination.Home.route, 95 | modifier = Modifier 96 | .fillMaxSize() 97 | .padding(contentPadding) 98 | ) { 99 | composable(Destination.Home.route) { 100 | HomeRoute() 101 | } 102 | 103 | composable(Destination.Favorites.route) { 104 | FavoriteRoute() 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Gif App


2 |

3 | Gif App is an Android application built using jetpack compose and other modern Android technology stacks and the MVVM architecture. 4 | The application just shows animated Stickers from Giphy's Rest API 5 |

6 |
7 | 8 |

9 | License 10 | API 11 | Build Status
12 |

13 | 14 | ## Screenshots 15 |

16 | 17 | 18 | 19 |

20 | 21 | ## Download 22 | 23 | 24 | 25 | 26 | ## Tech stack & Open-source libraries 27 | - Minimum SDK level 24 28 | - 100% [Kotlin](https://kotlinlang.org/) 29 | - [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) + [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/) to simplify code that executes asynchronously. 30 | - JetPack 31 | - [Compose](https://developer.android.com/jetpack/compose) - A modern toolkit for building native Android UI. 32 | - [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) - for dependency injection. 33 | - [Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) - dispose observing data when lifecycle state changes. 34 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - UI related data holder, lifecycle aware. 35 | - [Room](https://developer.android.com/training/data-storage/room) - to construct database and persist data more easily. 36 | - [Paging 3](https://developer.android.com/topic/libraries/architecture/paging/v3-overview) - helps to load and display pages of data from a larger dataset in order to use both network bandwidth and system resources more efficiently 37 | - [Material Design 3](https://developer.android.com/jetpack/compose/designsystems/material3) - Material 3 includes Material You personalization features like dynamic color, and is designed to be cohesive with the new visual style and system UI on Android 12 and above 38 | - [Mavericks](https://airbnb.io/mavericks) - Mavericks is the Android MVI framework from Airbnb. 39 | - [Lottie Android](https://github.com/airbnb/lottie-android) - Render After Effects animations natively on Android 40 | - [Ktor](https://github.com/square/retrofit) - consume the REST APIs. 41 | - [KSP](https://github.com/google/ksp) - Kotlin Symbol Processing API. 42 | - [Accompanist Pager](https://google.github.io/accompanist/pager/) - A library which provides paging layouts for Jetpack Compose. 43 | - [Coil](https://coil-kt.github.io/coil/compose/) - An image loading library for Android backed by Kotlin Coroutines. 44 | - [JUnit](https://developer.android.com/training/testing/local-tests) - a “Unit Testing” framework 45 | - [Mockk](https://mockk.io) - Mocking library for Kotlin 46 | - [Chucker](https://github.com/ChuckerTeam/chucker) - An HTTP inspector for Android & OkHTTP 47 | - [Secrets Gradle Plugin for Android](https://github.com/google/secrets-gradle-plugin) - A Gradle plugin for providing secrets to Android project. 48 | - [Faker](https://serpro69.github.io/kotlin-faker/) - kotlin-faker is a data-generation library intended for use during development and testing 49 | 50 | # Setup & Installation 51 | Gif App is using the [Giphy Api](https://developers.giphy.com/docs/api/#quick-start-guide) for constructing RESTful API. 52 | Create an account in order to have an api key and put it in `secrets.properties` file as following 53 | ``` 54 | giphyApiKey=Your Api Key 55 | ``` 56 | 57 | # Challenge and difficulties encountered 58 | - Actually the goal is to keep things simple, that why i decided to not use (remove) the Jetpack Pagination,because this has somme constraints that will led me to rethinks some parts of my architecture. 59 | I prefer to implement my own pagination logic in the future. 60 | 61 | ## Find this repository useful? :heart: 62 | Support it by joining __[stargazers](https://github.com/yveskalume/gif-app/stargazers)__ for this repository. :star: 63 | 64 | # License 65 | 66 | ``` 67 | Designed and developed by 2020 yveskalume (Yves Kalume) 68 | 69 | Licensed under the Apache License, Version 2.0 (the "License"); 70 | you may not use this file except in compliance with the License. 71 | You may obtain a copy of the License at 72 | 73 | http://www.apache.org/licenses/LICENSE-2.0 74 | 75 | Unless required by applicable law or agreed to in writing, software 76 | distributed under the License is distributed on an "AS IS" BASIS, 77 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | See the License for the specific language governing permissions and 79 | limitations under the License. 80 | ``` 81 | -------------------------------------------------------------------------------- /app/src/main/res/raw/loading_icon.json: -------------------------------------------------------------------------------- 1 | {"v":"5.8.1","fr":30,"ip":0,"op":50,"w":1366,"h":768,"nm":"circle","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[645,419,0],"ix":2,"l":2},"a":{"a":0,"k":[-38,35,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Glow","np":16,"mn":"ADBE Glo2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Glow Based On","mn":"ADBE Glo2-0001","ix":1,"v":{"a":0,"k":2,"ix":1}},{"ty":0,"nm":"Glow Threshold","mn":"ADBE Glo2-0002","ix":2,"v":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[132]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19,"s":[115]},{"t":40,"s":[141]}],"ix":2}},{"ty":0,"nm":"Glow Radius","mn":"ADBE Glo2-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[13]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17,"s":[110]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[60]},{"t":38,"s":[71]}],"ix":3}},{"ty":0,"nm":"Glow Intensity","mn":"ADBE Glo2-0004","ix":4,"v":{"a":0,"k":1.09,"ix":4}},{"ty":7,"nm":"Composite Original","mn":"ADBE Glo2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":7,"nm":"Glow Operation","mn":"ADBE Glo2-0006","ix":6,"v":{"a":0,"k":3,"ix":6}},{"ty":7,"nm":"Glow Colors","mn":"ADBE Glo2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Color Looping","mn":"ADBE Glo2-0008","ix":8,"v":{"a":0,"k":3,"ix":8}},{"ty":0,"nm":"Color Loops","mn":"ADBE Glo2-0009","ix":9,"v":{"a":0,"k":1.16,"ix":9}},{"ty":0,"nm":"Color Phase","mn":"ADBE Glo2-0010","ix":10,"v":{"a":0,"k":0,"ix":10}},{"ty":0,"nm":"A & B Midpoint","mn":"ADBE Glo2-0011","ix":11,"v":{"a":0,"k":0.5,"ix":11}},{"ty":2,"nm":"Color A","mn":"ADBE Glo2-0012","ix":12,"v":{"a":0,"k":[1,1,1,0],"ix":12}},{"ty":2,"nm":"Color B","mn":"ADBE Glo2-0013","ix":13,"v":{"a":0,"k":[0,0,0,0],"ix":13}},{"ty":7,"nm":"Glow Dimensions","mn":"ADBE Glo2-0014","ix":14,"v":{"a":0,"k":1,"ix":14}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[310,310],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.658],"y":[0.44]},"o":{"x":[0.316],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.681],"y":[0.783]},"o":{"x":[0.341],"y":[0.154]},"t":15,"s":[13.886]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":29,"s":[60.898]},{"t":49,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"t":11,"s":[0]},{"t":39,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.505023193359,0.010426758785,0.886274509804,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":49,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-38,35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":6,"op":186,"st":6,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[645,419,0],"ix":2,"l":2},"a":{"a":0,"k":[-38,35,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Glow","np":16,"mn":"ADBE Glo2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Glow Based On","mn":"ADBE Glo2-0001","ix":1,"v":{"a":0,"k":2,"ix":1}},{"ty":0,"nm":"Glow Threshold","mn":"ADBE Glo2-0002","ix":2,"v":{"a":0,"k":89,"ix":2}},{"ty":0,"nm":"Glow Radius","mn":"ADBE Glo2-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19,"s":[51]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":28,"s":[0]},{"t":37,"s":[44]}],"ix":3}},{"ty":0,"nm":"Glow Intensity","mn":"ADBE Glo2-0004","ix":4,"v":{"a":0,"k":1.03,"ix":4}},{"ty":7,"nm":"Composite Original","mn":"ADBE Glo2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":7,"nm":"Glow Operation","mn":"ADBE Glo2-0006","ix":6,"v":{"a":0,"k":1,"ix":6}},{"ty":7,"nm":"Glow Colors","mn":"ADBE Glo2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Color Looping","mn":"ADBE Glo2-0008","ix":8,"v":{"a":0,"k":3,"ix":8}},{"ty":0,"nm":"Color Loops","mn":"ADBE Glo2-0009","ix":9,"v":{"a":0,"k":1,"ix":9}},{"ty":0,"nm":"Color Phase","mn":"ADBE Glo2-0010","ix":10,"v":{"a":0,"k":0,"ix":10}},{"ty":0,"nm":"A & B Midpoint","mn":"ADBE Glo2-0011","ix":11,"v":{"a":0,"k":0.521,"ix":11}},{"ty":2,"nm":"Color A","mn":"ADBE Glo2-0012","ix":12,"v":{"a":0,"k":[0.988235294819,0.47531041503,0.019377160817,1],"ix":12}},{"ty":2,"nm":"Color B","mn":"ADBE Glo2-0013","ix":13,"v":{"a":0,"k":[0.149973079562,0.831372559071,0.55079627037,1],"ix":13}},{"ty":7,"nm":"Glow Dimensions","mn":"ADBE Glo2-0014","ix":14,"v":{"a":0,"k":1,"ix":14}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[310,310],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":17,"s":[0]},{"t":50,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.675],"y":[0.233]},"o":{"x":[0.341],"y":[0.979]},"t":0,"s":[0]},{"i":{"x":[0.69],"y":[0.715]},"o":{"x":[0.356],"y":[0.45]},"t":8,"s":[12.397]},{"i":{"x":[0.708],"y":[1]},"o":{"x":[0.373],"y":[0.529]},"t":26,"s":[64.421]},{"t":45,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.010426758785,0.886274509804,0.360765883502,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":41,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-38,35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /gradle/libraries.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | compose_ui = "1.3.3" 3 | nav_version = "2.5.3" 4 | hilt = "2.44.2" 5 | pager = "0.29.1-alpha" 6 | androidGradlePlugin = "7.4.1" 7 | kotlin = "1.8.10" 8 | composeCompiler = "1.4.2" 9 | ktor = "2.2.3" 10 | coil = "2.2.2" 11 | coroutine = "1.3.9" 12 | room = "2.5.0" 13 | lottie = "6.0.0" 14 | chucker = "3.5.2" 15 | mavericks = "3.0.2" 16 | paging = "3.1.1" 17 | modernStorage = "1.0.0-alpha06" 18 | 19 | [libraries] 20 | 21 | core-ktx = "androidx.core:core-ktx:1.9.0" 22 | lifecycle-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" 23 | kotlin-serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" 24 | splashscreen = "androidx.core:core-splashscreen:1.0.0-beta02" 25 | kotlinx-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" 26 | 27 | # hilt 28 | hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } 29 | hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } 30 | hilt-test = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } 31 | hilt-navigation-compose = "androidx.hilt:hilt-navigation-compose:1.0.0" 32 | 33 | # pager 34 | pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "pager" } 35 | pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "pager" } 36 | 37 | # compose 38 | compose-ui-ui = { module = "androidx.compose.ui:ui", version.ref = "compose_ui" } 39 | compose-ui-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose_ui" } 40 | compose-ui-androidtest = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose_ui" } 41 | compose-icons = "androidx.compose.material:material-icons-extended:1.4.3" 42 | compose-activity = "androidx.activity:activity-compose:1.7.1" 43 | compose-material = "androidx.compose.material:material:1.4.3" 44 | compose-material3 = "androidx.compose.material3:material3:1.1.0" 45 | compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "nav_version" } 46 | compose-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-compose:2.6.1" 47 | 48 | 49 | #compose debug 50 | compose-ui-tooling-debug = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose_ui" } 51 | compose-ui-debug = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose_ui" } 52 | 53 | ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 54 | ktor-contentnegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } 55 | ktor-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } 56 | ktor-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } 57 | ktor-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } 58 | 59 | coroutine = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutine" } 60 | coroutine-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutine" } 61 | 62 | room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } 63 | room-ksp = { module = "androidx.room:room-compiler", version.ref = "room" } 64 | room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } 65 | room-paging = { module = "androidx.room:room-paging", version.ref = "room" } 66 | room-test = { module = "androidx.room:room-testing", version.ref = "room" } 67 | 68 | # junit 69 | junit-test = "junit:junit:4.13.2" 70 | 71 | # android test 72 | junit-androidTest = "androidx.test.ext:junit:1.1.3" 73 | espresso-androidTest = "androidx.test.espresso:espresso-core:3.3.0" 74 | 75 | # mockk 76 | mockk = "io.mockk:mockk-android:1.13.4" 77 | 78 | # 3rd party 79 | coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } 80 | coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } 81 | 82 | # Dependencies of the included build-logic 83 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } 84 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } 85 | 86 | faker = "io.github.serpro69:kotlin-faker:1.13.0" 87 | 88 | lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" } 89 | 90 | chucker-debug = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" } 91 | chucker-release = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" } 92 | mavericks = { module = "com.airbnb.android:mavericks", version.ref = "mavericks" } 93 | mavericks-compose = { module = "com.airbnb.android:mavericks-compose", version.ref = "mavericks" } 94 | mavericks-testing = { module = "com.airbnb.android:mavericks-testing", version.ref = "mavericks" } 95 | 96 | paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging" } 97 | paging-compose = "androidx.paging:paging-compose:1.0.0-alpha18" 98 | 99 | timber = "com.jakewharton.timber:timber:5.0.1" 100 | 101 | accompanist-permision = "com.google.accompanist:accompanist-permissions:0.31.1-alpha" 102 | modernStorage-permission = { module = "com.google.modernstorage:modernstorage-permissions", version.ref = "modernStorage" } 103 | turbine = "app.cash.turbine:turbine:0.13.0" 104 | 105 | [bundles] 106 | core = ["core-ktx", "lifecycle-ktx"] 107 | compose = ["compose-ui-ui", "compose-ui-preview", "compose-ui-androidtest", "compose-icons", "compose-activity", "compose-material", "compose-navigation", "splashscreen", "compose-lifecycle-runtime"] 108 | compose-debug = ["compose-ui-tooling-debug", "compose-ui-debug"] 109 | pager = ["pager", "pager-indicators"] 110 | 111 | [plugins] 112 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } 113 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } 114 | android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } 115 | hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } 116 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 117 | 118 | 119 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/src/main/res/raw/favorite_icon.json: -------------------------------------------------------------------------------- 1 | {"v":"5.6.6","fr":60,"ip":0,"op":120,"w":300,"h":300,"nm":"Black - Fizz","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Кривые Group 326","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[10.5,9.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6.006,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,111.11]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.454,0.454,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":24.024,"s":[766.658,766.658,100]},{"t":32.032032032032,"s":[599.994,599.994,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.268,-2.309],[-2.371,-1.993],[2.195,-2.243],[0,0],[0.587,0],[0.41,0.419],[0,0],[-3.084,2.596]],"o":[[2.269,-2.309],[3.088,2.6],[0,0],[-0.41,0.417],[-0.583,0],[0,0],[-2.199,-2.244],[2.371,-1.998]],"v":[[0,-7.249],[8.302,-7.864],[8.736,1.212],[1.542,8.543],[0,9.19],[-1.542,8.538],[-8.736,1.209],[-8.302,-7.864]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.941000007181,0.090000002992,0.086000001197,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[10.5,9.19],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Группа 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Кривые heart","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400.5,301.5,0],"ix":2},"a":{"a":0,"k":[10.5,9.5,0],"ix":1},"s":{"a":0,"k":[595.232,595.232,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.484,-1.514],[0,0],[0.098,0.098],[0,0],[-2.1,1.768],[-1.542,-1.571],[0,0],[0,0],[-1.587,-1.337]],"o":[[0,0],[-0.099,0.098],[0,0],[-1.498,-1.526],[1.596,-1.341],[0,0],[0,0],[1.55,-1.58],[2.096,1.768]],"v":[[7.334,-0.174],[0.14,7.156],[-0.139,7.156],[-7.333,-0.174],[-7.034,-6.355],[-1.436,-5.924],[0,-4.46],[1.436,-5.924],[7.034,-6.359]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[3.088,2.6],[2.269,-2.309],[2.371,-1.998],[-2.199,-2.243],[0,0],[-0.583,0],[-0.41,0.418],[0,0]],"o":[[-2.371,-1.994],[-2.268,-2.309],[-3.084,2.596],[0,0],[0.41,0.419],[0.587,0],[0,0],[2.195,-2.243]],"v":[[8.302,-7.864],[0,-7.249],[-8.302,-7.864],[-8.736,1.208],[-1.542,8.537],[0,9.19],[1.542,8.542],[8.736,1.212]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22.022,"s":[0.713999968884,0.713999968884,0.713999968884,1]},{"t":24.024024024024,"s":[0.941176474094,0.090196080506,0.086274512112,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[10.5,9.19],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Группа 1","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Heart Small 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[-6.667,21.333,0],"ti":[24.157,-2.797,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[358.542,423.729,0],"to":[-15.833,1.833,0],"ti":[-2,62,0]},{"t":94.0940940940941,"s":[320.542,326.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":94.0940940940941,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Heart Small 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[-4.667,-14.167,0],"ti":[6.872,23.327,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[370.542,210.729,0],"to":[-11.833,-40.167,0],"ti":[-22,49,0]},{"t":114.114114114114,"s":[387.542,71.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":98.0980980980981,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Heart Small 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[8.167,18.833,0],"ti":[-45.591,-23.561,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[447.542,408.729,0],"to":[31.072,16.058,0],"ti":[-2,79,0]},{"t":114.114114114114,"s":[536.542,311.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":114.114114114114,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Heart Small 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[-15.167,-2,0],"ti":[18.187,16.144,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[307.542,283.729,0],"to":[-14.833,-13.167,0],"ti":[-43,86,0]},{"t":94.0940940940941,"s":[304.542,130.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":94.0940940940941,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Heart Small 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[13.333,-11.333,0],"ti":[-8.625,22.738,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[478.542,227.729,0],"to":[9.167,-24.167,0],"ti":[18,38,0]},{"t":84.0840840840841,"s":[475.542,111.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":84.0840840840841,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Heart Small 6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[-14,25,0],"ti":[37.303,1.864,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[305.042,366.729,0],"to":[-32.813,-1.639,0],"ti":[-6,79.5,0]},{"t":104.104104104104,"s":[258.542,224.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":104.104104104104,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Heart Small 7","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12.012,"s":[0]},{"t":18.018018018018,"s":[25]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":14.014,"s":[398.542,295.729,0],"to":[14.5,7.667,0],"ti":[-24.051,3.598,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44.044,"s":[485.542,341.729,0],"to":[21.167,-3.167,0],"ti":[24,89,0]},{"t":94.0940940940941,"s":[511.542,192.729,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":14.014,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44.044,"s":[125,125,100]},{"t":94.0940940940941,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.724,0],[1.199,-1.096],[1.752,0],[0,-3.724],[-1.411,-1.686],[0,0],[-1.38,1.752],[0,2.158]],"o":[[-1.752,0],[-1.199,-1.096],[-3.724,0],[0,2.145],[1.411,1.686],[0,0],[1.327,-1.685],[0,-3.724]],"v":[[4.547,-10.022],[0,-8.257],[-4.547,-10.022],[-11.289,-3.28],[-9.132,2.328],[-0.004,10.022],[9.14,2.288],[11.289,-3.28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.12012012012,"st":-106.106106106106,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Предварительная композиция 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[149.99999999999994,155.59999999999994,0],"ix":2},"a":{"a":0,"k":[400,300,0],"ix":1},"s":{"a":0,"k":[99.99999999999996,99.99999999999996,100],"ix":6}},"ao":0,"w":800,"h":600,"ip":0,"op":120.12012012012,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /app/src/main/res/raw/empty_icon.json: -------------------------------------------------------------------------------- 1 | {"v":"4.8.0","meta":{"g":"LottieFiles AE 1.1.0","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":77.0000031362743,"w":1000,"h":1000,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[480,506,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[41,-30],[34,-78],[-36.607,20.098],[40,-43],[-71,185],[26,-28]],"o":[[0,0],[-30.454,22.283],[-34,78],[51,-28],[-61.995,66.645],[70.57,-183.88],[-26,28]],"v":[[-142,44],[-139,140],[-352,106],[-171,308],[-204,227],[-313,168],[-413,67]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":30,"ix":1}},{"n":"g","nm":"gap","v":{"a":0,"k":1369,"ix":2}},{"n":"o","nm":"offset","v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":32,"s":[103]},{"t":70.0000028511585,"s":[-1303]}],"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":22.0000008960784,"op":1810.00007372281,"st":12.00000048877,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[500,500,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[41,-30],[34,-78],[-36.607,20.098],[40,-43],[-71,185],[26,-28]],"o":[[0,0],[-30.454,22.283],[-34,78],[51,-28],[-61.995,66.645],[70.57,-183.88],[-26,28]],"v":[[-142,44],[-139,140],[-352,106],[-171,308],[-204,227],[-313,168],[-413,67]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":12,"ix":1}},{"n":"g","nm":"gap","v":{"a":0,"k":1369,"ix":2}},{"n":"o","nm":"offset","v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[103]},{"t":58.0000023623884,"s":[-1303]}],"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":22.0000008960784,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"head/boxgirl2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[-5]},{"t":75.0000030548126,"s":[0]}],"ix":10},"p":{"a":0,"k":[504.173,279.4,0],"ix":2},"a":{"a":0,"k":[615.874,302.163,0],"ix":1},"s":{"a":0,"k":[83,83,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.203,0],[0,0],[0.188,-0.112],[0.266,-0.265],[0.11,-0.275],[-0.203,0],[0,0],[-0.188,0.113],[-0.266,0.266],[-0.11,0.274]],"o":[[0,0],[-0.231,0],[-0.322,0.194],[-0.206,0.206],[-0.041,0.103],[0,0],[0.232,0],[0.322,-0.194],[0.206,-0.205],[0.041,-0.104]],"v":[[3.115,-1.01],[-1.033,-1.01],[-1.711,-0.745],[-2.621,-0.032],[-3.193,0.682],[-3.115,1.01],[1.032,1.01],[1.711,0.745],[2.621,0.031],[3.193,-0.682]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[592.024,140.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.441,0],[0,0],[0.377,-0.376],[-0.441,0],[0,0],[-0.377,0.376]],"o":[[0,0],[-0.571,0],[-0.086,0.085],[0,0],[0.572,0],[0.085,-0.085]],"v":[[4.643,-1.01],[-2.561,-1.01],[-4.149,-0.032],[-4.643,1.01],[2.56,1.01],[4.149,0.032]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[589.932,134.862],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.203,0],[0,0],[0.187,-0.112],[0.266,-0.265],[0.109,-0.275],[-0.203,0],[0,0],[-0.188,0.113],[-0.266,0.265],[-0.11,0.274]],"o":[[0,0],[-0.232,0],[-0.323,0.194],[-0.206,0.206],[-0.042,0.103],[0,0],[0.232,0],[0.322,-0.193],[0.206,-0.206],[0.041,-0.104]],"v":[[2.569,-1.01],[-0.487,-1.01],[-1.165,-0.745],[-2.076,-0.032],[-2.647,0.682],[-2.57,1.01],[0.486,1.01],[1.165,0.745],[2.075,0.032],[2.647,-0.682]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[594.189,130.405],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.697,0.13],[1.381,-1.402],[-1.277,-1.162],[-0.539,-2.933],[1.291,0.371],[0.599,1.294],[-0.397,-0.857],[-2.347,3.604],[1.566,1.335],[1.014,0.42],[0.075,0.558],[-0.756,-0.141]],"o":[[-1.872,-0.349],[-1.032,1.047],[1.762,1.602],[0.338,1.839],[-1.421,-0.409],[-0.212,-0.458],[1.982,4.281],[1.124,-1.727],[-0.848,-0.723],[-0.383,-0.158],[-0.118,-0.875],[0.82,0.152]],"v":[[2.104,-7.117],[-2.682,-5.892],[-3.188,-2.149],[2.92,2.141],[-0.213,3.831],[-3.175,1.063],[-5.296,3.185],[4.569,3.198],[3.753,-1.848],[0.801,-3.374],[-1.262,-4.469],[0.261,-4.868]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[602.895,136.398],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-5.784,2.336]],"o":[[0,0],[0,0]],"v":[[-6.716,-0.848],[6.716,-1.168]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.247058838489,0.560784313725,0.819607902976,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[605.021,201.209],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-5.784,2.336]],"o":[[0,0],[0,0]],"v":[[-6.716,-2.008],[6.716,-0.328]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.247058838489,0.560784313725,0.819607902976,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[582.244,200.369],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":3,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.901,0.241],[8.078,-4.259],[-3.323,-5.913],[-6.748,-0.677],[-6.62,1.477],[0,0]],"o":[[-0.963,-0.223],[-21.25,-5.699],[-6,3.163],[3.324,5.912],[6.748,0.678],[0,0],[0,0]],"v":[[22.043,0.722],[19.248,0.025],[-14.734,-11.997],[-18.914,5.764],[-1.981,15.578],[22.237,12.775],[19.248,0.025]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.247058838489,0.560784313725,0.819607902976,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[543.515,174.404],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":3,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-27.722,-3.24],[2.66,-27.783],[19.547,5.242],[8.078,-4.258],[-3.324,-5.912],[-6.748,-0.677],[-6.62,1.476],[0,0]],"o":[[0,0],[3.818,-27.648],[27.722,3.24],[-63.214,18.371],[-21.25,-5.699],[-6,3.163],[3.323,5.912],[6.748,0.678],[0,0],[0,0]],"v":[[-72.745,16.895],[-29.706,4.359],[30.081,-41.908],[77.588,16.899],[-38.763,28.917],[-72.745,16.895],[-76.924,34.656],[-59.992,44.47],[-35.774,41.668],[-38.763,28.917]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.462745127958,0.705882352941,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[601.526,145.512],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":3,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.734,-12.941],[-15.624,-0.096],[-3.417,15.247]],"o":[[2.734,12.941],[3.23,15.288],[15.625,0.095],[0,0]],"v":[[-36.33,-25.893],[-31.201,-1.615],[2.397,25.799],[36.33,-1.201]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[602.33,214.252],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[6.091,-7.297]],"o":[[-0.924,12.992],[0,0]],"v":[[5.451,-19.487],[-5.451,19.487]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[594.411,259.908],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[1.404,-13.995],[-7.319,-22.945],[-22.358,-8.953],[-13.565,19.901],[22.678,8.11],[-0.689,25.725],[5.686,1.105]],"o":[[7.301,7.834],[-2.405,23.964],[7.32,22.944],[22.358,8.953],[13.565,-19.9],[-24.231,-8.665],[0.155,-5.79],[-5.686,-1.106]],"v":[[-67.624,-74.46],[-60.836,-50.411],[-55.453,11.567],[-9.899,64.62],[54.789,47.744],[37.918,-12.56],[2.322,-69.611],[-7.677,-82.077]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[{"i":[[0,0],[1.404,-13.995],[-7.319,-22.945],[-22.358,-8.953],[-13.565,19.901],[22.678,8.11],[-0.689,25.725],[5.686,1.105]],"o":[[7.301,7.834],[-2.405,23.964],[7.32,22.944],[22.358,8.953],[13.565,-19.9],[-24.231,-8.665],[0.155,-5.79],[-5.686,-1.106]],"v":[[-67.624,-74.46],[-60.836,-50.411],[-56.182,21.178],[-10.628,74.23],[54.06,57.355],[37.189,-2.949],[2.322,-69.611],[-7.677,-82.077]],"c":true}]},{"t":74.0000030140818,"s":[{"i":[[0,0],[1.404,-13.995],[-7.319,-22.945],[-22.358,-8.953],[-13.565,19.901],[22.678,8.11],[-0.689,25.725],[5.686,1.105]],"o":[[7.301,7.834],[-2.405,23.964],[7.32,22.944],[22.358,8.953],[13.565,-19.9],[-24.231,-8.665],[0.155,-5.79],[-5.686,-1.106]],"v":[[-67.624,-74.46],[-60.836,-50.411],[-55.453,11.567],[-9.899,64.62],[54.789,47.744],[37.918,-12.56],[2.322,-69.611],[-7.677,-82.077]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156922583,1,0.984313785329,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[692.302,244.019],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":3,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"arms/boxgirl2 Outlines","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":7,"s":[-18]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13.824,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[-18]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[-18]},{"t":54.0000021994651,"s":[-18]}],"ix":10},"p":{"a":0,"k":[506.439,444.242,0],"ix":2},"a":{"a":0,"k":[534.439,398.222,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.407],[3.407,0],[0,3.407],[-3.407,0]],"o":[[0,3.407],[-3.407,0],[0,-3.407],[3.407,0]],"v":[[6.169,0],[0,6.169],[-6.168,0],[0,-6.169]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[632.081,461.246],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":7,"s":[{"i":[[0,0],[-22.517,-0.724],[-10.788,37.86]],"o":[[25.288,-7.398],[36.098,3.218],[0,0]],"v":[[-38.207,26.791],[38.078,30.823],[87.513,-6.807]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":14,"s":[{"i":[[0,0],[-22.888,-13.053],[-2.749,18.008]],"o":[[25.288,-7.398],[-17.303,-28.448],[0,0]],"v":[[-38.207,26.791],[38.207,35.761],[21.086,-17.93]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":21,"s":[{"i":[[0,0],[-22.517,-0.724],[-10.788,37.86]],"o":[[25.288,-7.398],[36.098,3.218],[0,0]],"v":[[-38.207,26.791],[38.078,30.823],[87.513,-6.807]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":27.176,"s":[{"i":[[0,0],[-22.888,-13.053],[-2.749,18.008]],"o":[[25.288,-7.398],[-17.303,-28.448],[0,0]],"v":[[-38.207,26.791],[38.207,35.761],[21.086,-17.93]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":37,"s":[{"i":[[0,0],[-22.517,-0.724],[-10.788,37.86]],"o":[[25.288,-7.398],[36.098,3.218],[0,0]],"v":[[-38.207,26.791],[38.078,30.823],[87.513,-6.807]],"c":false}]},{"t":54.0000021994651,"s":[{"i":[[0,0],[-22.517,-0.724],[-10.788,37.86]],"o":[[25.288,-7.398],[36.098,3.218],[0,0]],"v":[[-38.207,26.791],[38.078,30.823],[87.513,-6.807]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[636.453,358.075],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.991,-1.935],[1.393,-9.397],[3.254,-8.925],[-1.049,-10.002],[0,0],[-11.042,-10.598],[20.008,35.512]],"o":[[3.543,-0.536],[7.976,5.16],[-1.392,9.397],[-3.253,8.925],[0,0],[0.418,10.558],[7.218,-35.73],[0,0]],"v":[[-34.042,-55.116],[-23.763,-53.142],[-14.661,-27.77],[-23.79,-0.843],[-27.997,28.159],[-28.105,26.975],[6.752,55.652],[14.035,-54.499]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.462745127958,0.705882352941,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[635.71,439.352],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-19.251,-20.715],[19.252,-11.749],[12.525,20.715],[-9.779,13.975]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":3,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.643137254902,0.823529471603,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[617.497,415.331],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0.037,-6.22],[4.993,-23.691],[-34.033,29.139],[0,0]],"o":[[0,0],[0,0],[-4.999,-3.701],[0,0],[-3.982,18.892],[0,0],[0,0]],"v":[[55.805,-7.473],[11.559,-17.777],[-4.366,-29.569],[-16.488,-23.507],[-52.904,-14.08],[1.026,8.632],[56.886,25.512]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[554.414,404.877],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-7.976,-5.16],[1.393,-9.397],[3.254,-8.925],[-1.049,-10.001],[2.982,11.029]],"o":[[0.571,-9.482],[7.975,5.161],[-1.393,9.396],[-3.254,8.925],[-2.982,-11.029],[0,0]],"v":[[-16.19,-29.242],[5.695,-38.07],[14.797,-12.698],[5.668,14.229],[1.461,43.23],[-8.484,4.143]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.247058838489,0.560784313725,0.819607902976,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[606.252,424.28],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":3,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[{"i":[[0,0],[76.287,8.852],[17.248,1.993]],"o":[[77.45,65.728],[-17.247,-2.001],[0,0]],"v":[[63.279,-99.083],[-7.091,82.071],[-65.557,72.343]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":14,"s":[{"i":[[0,0],[84.904,4.88],[17.248,1.993]],"o":[[83.039,26.063],[-17.334,-0.997],[0,0]],"v":[[-26.996,-99.139],[-19.347,87.726],[-65.557,72.343]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":21,"s":[{"i":[[0,0],[76.287,8.852],[17.248,1.993]],"o":[[77.45,65.728],[-17.247,-2.001],[0,0]],"v":[[63.279,-99.083],[-7.091,82.071],[-65.557,72.343]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":27,"s":[{"i":[[0,0],[84.904,4.88],[17.248,1.993]],"o":[[88.231,6.819],[-17.334,-0.997],[0,0]],"v":[[-46.273,-107.574],[-19.347,87.726],[-65.557,72.343]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":37,"s":[{"i":[[0,0],[76.287,8.852],[17.248,1.993]],"o":[[77.45,65.728],[-17.247,-2.001],[0,0]],"v":[[63.279,-99.083],[-7.091,82.071],[-65.557,72.343]],"c":false}]},{"t":54.0000021994651,"s":[{"i":[[0,0],[76.287,8.852],[17.248,1.993]],"o":[[77.45,65.728],[-17.247,-2.001],[0,0]],"v":[[63.279,-99.083],[-7.091,82.071],[-65.557,72.343]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.462745127958,0.705882352941,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[717.313,386.909],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":3,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"BOX/boxgirl2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":7,"s":[-11]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.961,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21.216,"s":[-11]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26.903,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36.569,"s":[-11]},{"t":76.0000030955435,"s":[-11]}],"ix":10},"p":{"s":true,"x":{"a":0,"k":497.232,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.525],"y":[0.999]},"o":{"x":[0.167],"y":[0.012]},"t":7,"s":[534.782]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.714],"y":[-0.001]},"t":14.961,"s":[473.393]},{"i":{"x":[0.086],"y":[1.001]},"o":{"x":[0.333],"y":[0]},"t":20.648,"s":[554.791]},{"i":{"x":[0.345],"y":[1.257]},"o":{"x":[0.475],"y":[0.001]},"t":26.903,"s":[484.088]},{"i":{"x":[0.058],"y":[8.039]},"o":{"x":[0.207],"y":[-15.641]},"t":37.138,"s":[534.782]},{"t":76.0000030955435,"s":[534.782]}],"ix":4}},"a":{"a":0,"k":[572.5,586.5,0],"ix":1},"s":{"a":0,"k":[83,83,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.345,-40.918],[0,0],[0,0],[0,0]],"o":[[0,0],[5.794,-40.405],[0,0],[0,0]],"v":[[2.264,43.63],[-8.285,42.896],[-2.262,-43.63],[8.286,-42.896]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[307.842,426.297],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.221,1.428]],"o":[[0,0],[0,0]],"v":[[0.841,-83.44],[-2.611,82.013]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[306.142,427.058],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-69.992,1.014]],"o":[[69.993,-1.014],[0,0]],"v":[[-112.026,3.123],[112.026,-3.123]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[477.19,351.902],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-38.969,-5.99],[1.82,-62.867]],"o":[[41.66,9.085],[-1.821,62.868],[0,0]],"v":[[-56.581,-95.724],[56.581,-72.475],[52.313,95.724]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.866666726505,0.737254901961,0.549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[308.254,427.051],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-43.799,3.2],[-73.661,-9.244],[-9.976,-73.578],[68.598,2.806],[42.988,11.764],[6.219,42.049]],"o":[[64.703,16],[-14.075,53.761],[-67.608,11.939],[-42.988,-11.764],[7.574,-63.979],[43.799,-3.2]],"v":[[-15.044,-99.102],[169.981,-81.65],[161.058,90.364],[-59.207,94.101],[-169.981,65.062],[-167.562,-99.102]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.866666726505,0.737254901961,0.549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[419.236,430.43],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17.235,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.475],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[18.337,-0.432],[53.327,11.084],[-23.981,4.669]],"o":[[-25.774,-4.099],[-48.63,-17.126],[24.904,-3.29],[0,0]],"v":[[81.443,-11.521],[31.961,-12.457],[-76.764,-40.908],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22.921,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28.608,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[18.337,-0.432],[53.327,11.084],[-23.981,4.669]],"o":[[-25.774,-4.099],[-48.63,-17.126],[24.904,-3.29],[0,0]],"v":[[81.443,-11.521],[31.961,-12.457],[-76.764,-40.909],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":46,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[{"i":[[0,0],[5.295,-25.538],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[68.394,55.122],[-39.877,9.497],[-29.331,-40.56]],"c":false}]},{"t":77.0000031362743,"s":[{"i":[[0,0],[4.29,-26.25],[54.595,20.476],[-5.348,26.556]],"o":[[-5.881,28.24],[-48.63,-17.126],[7.005,-28.32],[0,0]],"v":[[81.443,-11.521],[72.476,56.222],[-39.877,9.497],[-29.331,-40.56]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.866666726505,0.737254901961,0.549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[278.586,536.051],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":3,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.976],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.352,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[-7.408,-22.677],[85.647,-5.975],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.353,-34.341],[110.216,25.436],[-110.292,41.803],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16.666,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.975],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.353,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[-29.356,-13.941],[84.442,-5.729],[19.387,14.399]],"o":[[13.559,13.713],[-83.638,8.019],[-32.035,-13.191],[0,0]],"v":[[96.353,-34.341],[136.329,-10.87],[-68.498,3.481],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22.921,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.976],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.352,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28.608,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.976],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.352,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[-29.356,-13.941],[84.442,-5.729],[19.387,14.399]],"o":[[13.559,13.713],[-83.638,8.019],[-32.035,-13.191],[0,0]],"v":[[96.353,-34.341],[136.329,-10.87],[-68.498,3.481],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":46,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.976],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.352,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":52,"s":[{"i":[[0,0],[4.341,-23.172],[85.647,-5.976],[-5.305,25.866]],"o":[[-4.364,20.108],[-80.626,8.376],[3.168,-26.192],[0,0]],"v":[[96.352,-34.341],[83.496,23.989],[-128.163,39.03],[-118.704,-27.565]],"c":false}]},{"t":77.0000031362743,"s":[{"i":[[0,0],[-3.518,-24.757],[85.647,-5.976],[4.373,25.634]],"o":[[1.938,21.197],[-80.626,8.376],[-2.305,-27.236],[0,0]],"v":[[96.352,-34.341],[106.656,24.809],[-115.038,40.968],[-118.704,-27.565]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.866666726505,0.737254901961,0.549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[483.54,552.116],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":3,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Legs/boxgirl2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[477.232,513.782,0],"ix":2},"a":{"a":0,"k":[572.5,586.5,0],"ix":1},"s":{"a":0,"k":[83,83,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.742,-7.41],[-14.493,-3.584],[-25.452,2.565]],"o":[[-5.443,5.57],[2.029,20.261],[13.455,3.327],[0,0]],"v":[[-48.474,-30.191],[-57.779,-10.254],[-0.625,23.639],[58.521,27.626]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[647.342,989.901],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-8.064,5.222]],"o":[[0,0],[0,0]],"v":[[-11.624,-6.953],[11.624,1.731]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[618.704,1014.636],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,3.45],[0,0],[0,0],[0,0],[0,0],[3.45,0]],"o":[[-3.45,0],[0,0],[0,0],[0,0],[0,0],[0,3.45],[0,0]],"v":[[-66.997,10.39],[-73.244,4.143],[-73.244,-10.39],[-70.416,-10.39],[73.244,-10.39],[73.244,4.143],[66.997,10.39]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[630.328,1057.988],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0.745,2.543],[-7.412,2.56],[0,0],[0,0]],"o":[[0,0],[0,0],[-2.561,-2.245],[-2.206,-7.525],[0,0],[0,0],[0,0]],"v":[[74.419,-1.574],[75.315,23.542],[-68.346,23.542],[-73.109,16.217],[-63.465,-1.762],[-21.177,-16.372],[-11.369,-23.542]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[628.258,1024.055],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.406,-7.312],[-14.756,-2.265],[-25.117,4.846]],"o":[[0,20.776],[3.845,19.996],[13.701,2.102],[0,0]],"v":[[-30.309,-32.271],[-58.915,0.168],[1.056,28.779],[60.321,27.425]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[561.487,975.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-7.561,5.927]],"o":[[0,0],[0,0]],"v":[[-11.967,-6.242],[11.968,0.315]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[519.21,1005.327],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[-3.437,0.31]],"o":[[0,0],[0,0],[0,0],[0.311,3.436],[0,0]],"v":[[74.051,5],[71.843,-16.903],[-74.051,-3.715],[-72.448,10.933],[-65.663,16.593]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[535.094,1047.055],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.577,12.468],[-1.576,-12.468]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[605.36,1017.685],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.874,-7.296],[-2.753,-2.005]],"o":[[0,0],[0,0],[-7.151,3.218],[0.971,2.467],[0,0]],"v":[[30.393,-26.012],[21.269,-17.987],[-19.532,0.37],[-27.519,19.143],[-22.114,26.012]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[485.974,1017.073],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10.98,"s":[{"i":[[0,0],[-3.442,-105.571]],"o":[[-18.704,112.217],[0,0]],"v":[[-9.493,-158.357],[9.494,158.357]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14.393,"s":[{"i":[[0,0],[-3.442,-105.571]],"o":[[3.442,105.571],[0,0]],"v":[[-9.493,-158.357],[9.494,158.357]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20.648,"s":[{"i":[[0,0],[-3.442,-105.571]],"o":[[-18.704,112.217],[0,0]],"v":[[-9.493,-158.357],[9.494,158.357]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25.765,"s":[{"i":[[0,0],[-3.442,-105.571]],"o":[[3.442,105.571],[0,0]],"v":[[-9.493,-158.357],[9.494,158.357]],"c":false}]},{"t":32.588751327367,"s":[{"i":[[0,0],[-3.442,-105.571]],"o":[[-18.704,112.217],[0,0]],"v":[[-9.493,-158.357],[9.494,158.357]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[519.695,753.384],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10.98,"s":[{"i":[[0,0],[3.081,-104.652]],"o":[[-11.201,108.609],[0,0]],"v":[[2.06,-160.308],[6.06,160.309]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14.393,"s":[{"i":[[0,0],[3.081,-104.652]],"o":[[5.242,106.157],[0,0]],"v":[[2.06,-160.308],[6.06,160.309]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20.648,"s":[{"i":[[0,0],[3.081,-104.652]],"o":[[-11.201,108.609],[0,0]],"v":[[2.06,-160.308],[6.06,160.309]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25.765,"s":[{"i":[[0,0],[3.081,-104.652]],"o":[[5.242,106.156],[0,0]],"v":[[2.06,-160.308],[6.06,160.309]],"c":false}]},{"t":32.588751327367,"s":[{"i":[[0,0],[3.081,-104.652]],"o":[[-11.201,108.609],[0,0]],"v":[[2.06,-160.308],[6.06,160.309]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[591.424,810.794],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10.98,"s":[{"i":[[0,0],[-21.928,-106.278]],"o":[[-33.777,91.486],[0,0]],"v":[[12.646,-187.238],[21.131,187.238]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14.393,"s":[{"i":[[0,0],[-10.66,-108.482]],"o":[[-4.585,92.5],[0,0]],"v":[[12.646,-187.238],[21.131,187.238]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20.648,"s":[{"i":[[0,0],[-21.928,-106.278]],"o":[[-33.777,91.486],[0,0]],"v":[[12.646,-187.238],[21.131,187.238]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25.765,"s":[{"i":[[0,0],[-10.66,-108.482]],"o":[[-4.585,92.5],[0,0]],"v":[[12.646,-187.238],[21.131,187.238]],"c":false}]},{"t":32.588751327367,"s":[{"i":[[0,0],[-21.928,-106.278]],"o":[[-33.777,91.486],[0,0]],"v":[[12.646,-187.238],[21.131,187.238]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[685.846,756.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Body/boxgirl2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10.98,"s":[477.232,513.782,0],"to":[0,-1.167,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14.393,"s":[477.232,506.782,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20.648,"s":[477.232,513.782,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25.765,"s":[477.232,506.782,0],"to":[0,0,0],"ti":[0,-1.167,0]},{"t":32.588751327367,"s":[477.232,513.782,0]}],"ix":2},"a":{"a":0,"k":[572.5,586.5,0],"ix":1},"s":{"a":0,"k":[83,83,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[{"i":[[0,0],[-1.842,-18.593],[20.162,4.991],[50.908,0.473],[-108.373,217.759],[-16.355,0],[0,0],[0,0],[-16.432,-8.614]],"o":[[6.366,18.406],[2.049,20.67],[-36.237,-8.969],[0,0],[10.336,-20.768],[0,0],[0,0],[18.522,-12.748],[0,0]],"v":[[132.53,-11.826],[161.147,106.417],[106.529,126.902],[-0.019,112.697],[-47.61,-79.325],[-1.019,-144.415],[23.696,-102.197],[71.999,-130.707],[125.265,-134.421]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[-1.842,-18.593],[20.162,4.991],[50.908,0.473],[-104.743,241.812],[-16.355,0],[0,0],[0,0],[-16.432,-8.614]],"o":[[6.366,18.406],[2.049,20.67],[-36.237,-8.969],[0,0],[9.221,-21.286],[0,0],[0,0],[18.522,-12.748],[0,0]],"v":[[132.53,-11.826],[161.147,106.417],[106.529,126.902],[-0.019,112.697],[-75.32,-116.675],[-1.019,-144.415],[23.696,-102.197],[71.999,-130.707],[125.265,-134.421]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[-1.842,-18.593],[20.162,4.991],[50.908,0.473],[-108.373,217.759],[-16.355,0],[0,0],[0,0],[-16.432,-8.614]],"o":[[6.366,18.406],[2.049,20.67],[-36.237,-8.969],[0,0],[10.336,-20.768],[0,0],[0,0],[18.522,-12.748],[0,0]],"v":[[132.53,-11.826],[161.147,106.417],[106.529,126.902],[-0.019,112.697],[-47.61,-88.964],[-1.019,-144.415],[23.696,-102.197],[71.999,-130.707],[125.265,-134.421]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[0,0],[-1.842,-18.593],[20.162,4.991],[50.908,0.473],[-108.373,217.759],[-16.355,0],[0,0],[0,0],[-16.432,-8.614]],"o":[[6.366,18.406],[2.049,20.67],[-36.237,-8.969],[0,0],[10.336,-20.768],[0,0],[0,0],[18.522,-12.748],[0,0]],"v":[[132.53,-11.826],[161.147,106.417],[106.529,126.902],[-0.019,112.697],[-63.272,-105.831],[-1.019,-144.415],[23.696,-102.197],[71.999,-130.707],[125.265,-134.421]],"c":false}]},{"t":33.0000013441176,"s":[{"i":[[0,0],[-1.842,-18.593],[20.162,4.991],[50.908,0.473],[-108.373,217.759],[-16.355,0],[0,0],[0,0],[-16.432,-8.614]],"o":[[6.366,18.406],[2.049,20.67],[-36.237,-8.969],[0,0],[10.336,-20.768],[0,0],[0,0],[18.522,-12.748],[0,0]],"v":[[132.53,-11.826],[161.147,106.417],[106.529,126.902],[-0.019,112.697],[-47.61,-79.325],[-1.019,-144.415],[23.696,-102.197],[71.999,-130.707],[125.265,-134.421]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.462745127958,0.705882352941,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[559.529,428.725],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Layer 8/boxgirl2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[477.232,513.782,0],"ix":2},"a":{"a":0,"k":[572.5,586.5,0],"ix":1},"s":{"a":0,"k":[83,83,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[47.878,-14.752],[3.234,-71.293],[-5.895,-75.093],[33.038,-81.93],[-85.728,-107.22],[-68.517,107.681],[-69.828,139.983],[-38.029,115.117],[46.807,57.257],[162.699,89.342]],"o":[[-55.539,17.112],[-3.198,70.52],[6.233,79.398],[-49.442,122.608],[99.596,124.565],[62.205,-97.761],[57.996,-116.263],[25.413,-76.926],[-71.866,-87.91],[-72.635,-39.886]],"v":[[-164.114,-536.141],[-273.101,-369.18],[-225.408,-209.28],[-297.757,-3.278],[-280.381,426.328],[113.978,424.615],[140.88,74.349],[340.696,-142.095],[303.58,-375.651],[-19.035,-471.694]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.972549079446,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[655.005,614.237],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1798.00007323404,"st":0,"bm":0}],"markers":[]} --------------------------------------------------------------------------------