Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heartstone cards app by Niels Masdorp #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Stores user specific settings ##
.gradle
.idea/
.settings/
.idea
*.iml

## could be located in different module directories ##
*.iml

## generated content ##
bin/
out/
gen/
build/

## local machine properties ##
local.properties

## folder that contains all test reports ##
test-reports/

## Test server created by calabash ##
test_servers/

## Misc ##
AndroidManifest.out.xml
com_crashlytics_export_strings.xml
crashlytics-build.properties
screenshot_*
.DS_Store
Thumbs.db
desktop.ini
128 changes: 26 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,116 +1,40 @@
## Introduction
## Heartstone Android by Niels Masdorp

Hsiao here at Splendo is a very enthusiastic casual Hearthstone player. He is also a user of the KLM houses apps ([iOS](https://itunes.apple.com/nl/app/klm-houses/id371664245?l=en&mt=8) / [Android](https://play.google.com/store/apps/details?id=com.klm.mobile.houses&hl=en))
### Architecture
This app is separated in 2 modules:

He wants you to build a web app that has similar UI/UX. Similar way to go from the grid view to detail view, and also being able to scroll through the detail views like a carousel (hint: download the Houses app and have a look at how it works) but he wants the app to show Hearthstone card images.
- app: Android module that is responsible for showing the actual app, it uses the MVP architecture for the presentation layer.
This module also contains the data providers
- domain: This is where all the business logic is located, it is a pure Java module with no Android related dependencies

We have supplied you with a json file (`cards.json`) containing all the Heartstone cards currently available.
In order to create a clear separation of concerns and to separate low level components (UI) from the high level components
(Entities and Use Cases) I have used the [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html)
proposed by Uncle Bob.

Hsiao is especially interested in the app showing `Legendary` cards with the `Deathrattle Mechanic`, below are examples of such a cards :
### Third party libraries

```json
{
"cardId": "FP1_014",
"name": "Stalagg",
"cardSet": "Naxxramas",
"type": "Minion",
"rarity": "Legendary",
"cost": 5,
"attack": 7,
"health": 4,
"text": "<b>Deathrattle:</b> If Feugen also died this game, summon Thaddius.",
"flavor": "Stalagg want to write own flavor text. \"STALAGG AWESOME!\"",
"artist": "Dany Orizio",
"collectible": true,
"elite": true,
"playerClass": "Neutral",
"howToGet": "Unlocked in The Construct Quarter, in the Naxxramas adventure.",
"howToGetGold": "Crafting unlocked in The Construct Quarter, in the Naxxramas adventure.",
"img": "http://wow.zamimg.com/images/hearthstone/cards/enus/original/FP1_014.png",
"imgGold": "http://wow.zamimg.com/images/hearthstone/cards/enus/animated/FP1_014_premium.gif",
"locale": "enUS",
"mechanics": [
{
"name": "Deathrattle"
}
]
}
```
In order to have proper dependency injection to tie all these components together I have chosen to use the
well known and community supported Dagger 2 library. For image loading I have chosen to use Glide, which is just a preference
over something like Picasso.

and
For logging I have used Jake Wharton's Timber. And lastly to create asynchronous streams I have used RxJava 2. For this project
I could have gone without since there is not a lot of data flowing through the application, but I chose to include it
to show that I am comfortable using it.

```json
{
"cardId": "CFM_902",
"name": "Aya Blackpaw",
"cardSet": "Mean Streets of Gadgetzan",
"type": "Minion",
"rarity": "Legendary",
"cost": 6,
"attack": 5,
"health": 3,
"text": " <b>Battlecry and Deathrattle:</b> Summon a <b>Jade Golem</b>.",
"flavor": "Though young, Aya took over as the leader of Jade Lotus through her charisma and strategic acumen when her predecessor was accidentally crushed by a jade golem.",
"artist": "Glenn Rane",
"collectible": true,
"elite": true,
"playerClass": "Neutral",
"multiClassGroup": "Jade Lotus",
"classes": [
"Druid",
"Rogue",
"Shaman"
],
"img": "http://media.services.zam.com/v1/media/byName/hs/cards/enus/CFM_902.png",
"imgGold": "http://media.services.zam.com/v1/media/byName/hs/cards/enus/animated/CFM_902_premium.gif",
"locale": "enUS",
"mechanics": [
{
"name": "Jade Golem"
},
{
"name": "Battlecry"
},
{
"name": "Deathrattle"
}
]
}
```
### Data

## Assignment
Cards: I have not chosen to host any data on a backend. I stored the JSON file in /assets and loaded it into memory.

You are free to choose the patterns and architectures to create this web app, the requirements are :
Favorites: Are stored in Shared Preferences

### Backend
Both data storage solutions are easily interchangeable with other solutions due to the architecture (e.g. implement storage interface
and bind new implementation in Dagger module)

* Create an API using a Java (plain java or Groovy/Cotlin) backend allowing you to get card information for at least legendary deathrattle cards
* The API should also support filtering based on relevant request parameters. Ideally, the API should enable the following, listed from easy to hard:
* filter by least the following fields: `type`, `rarity`, `classes`, and `mechanics`
* return sorted results (for example, alphabetically sorted), supporting both ascending and descending
* (optional) return the results by pages (based on a page size request parameter), iterating over the pages are maintained by a cursor which is included in the response, this cursor is used in the subsequent request
### Filtering

### Web Application
Data repository accepts a request model with criteria for cards and is being used to query legendary cards with a certain
mechanic, this can of course be extended to support more criteria as well.

* Create the web app using JavaScript. You can use either plain JavaScript or a Framework of your choice
* Show the card images in a grid like the houses app
* when user click on a grid item , navigate to the card detail view where you can display more information regarding the card ( what you would like to show and how is up to you ), when in detail view the navigation to the next and previous card should be the same as the Houses App
* The user should be able to set a card as favourite and this info should be persisted when the app closes, how to show cards that are tagged as favourites and how to persist that information is up to you
### Sorting


## What we would like to see

* Proper handling of asynchonous calls
* Clean code
* Relevant design patterns
* Javascript best practices
* UI should remain responsive during content loading
* Should you use 3rd party libraries and frameworks please motivate your choice
* Unit tests
* Writing the backend using Google AppEngine is a plus, but feel free to use Amazon AWS, Tomcat or anything you prefer for handling your API calls

## Finally

To submit your result, fork this repository. When you are satisfied with your result, create a Pull Request. Make sure your backend is up and running somewhere for the duration of the review and tell us in the comments where to find it.

Good Luck!
The request model also supports a sorting strategy (asc and desc), currently, this sorts the list by the name of the card
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
108 changes: 108 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

ext {
VERSION_NAME = "1.0.0"
VERSION_CODE = 1
}

androidExtensions {
experimental = true
}

android {
compileSdkVersion 27
defaultConfig {
applicationId "com.nielsmasdorp.heartstone"
minSdkVersion 21
targetSdkVersion 27
versionCode VERSION_CODE
versionName VERSION_NAME
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
minifyEnabled false

versionNameSuffix "-debug"
applicationIdSuffix ".debug"
}
acceptance {
signingConfig signingConfigs.debug
initWith buildTypes.debug

versionNameSuffix "-acceptance"
applicationIdSuffix ".acceptance"

minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

matchingFallbacks = ['release']
}
release {
minifyEnabled true
useProguard true

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/rxjava.properties'
exclude 'META-INF/rxkotlin.properties'
exclude 'META-INF/rxkotlin_main.kotlin_module'
}
}

dependencies {
implementation project(':domain')

implementation "com.android.support:appcompat-v7:$android_support_version"
implementation "com.android.support:recyclerview-v7:$android_support_version"
implementation "com.android.support:design:$android_support_version"
implementation "com.android.support:cardview-v7:$android_support_version"
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

// Gson
implementation 'com.google.code.gson:gson:2.8.4'

//Dagger
api "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
api("com.google.dagger:dagger-android-support:$dagger_version", {
exclude group: 'com.google.code.findbugs'
})

// Jake
api 'com.jakewharton.timber:timber:4.5.1'

// Glide
implementation 'com.github.bumptech.glide:glide:4.7.1'
kapt 'com.github.bumptech.glide:compiler:4.7.1'

// ReactiveX
api 'io.reactivex.rxjava2:rxandroid:2.0.1'
api "io.reactivex.rxjava2:rxjava:$reactive_x_version"
api "io.reactivex.rxjava2:rxkotlin:$reactive_x_version"
api 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
testApi 'io.reactivex.rxjava2:rxandroid:2.0.1'
testApi "io.reactivex.rxjava2:rxjava:$reactive_x_version"
testApi "io.reactivex.rxjava2:rxkotlin:$reactive_x_version"

testApi 'junit:junit:4.12'
testApi 'org.mockito:mockito-core:2.11.0'
testApi 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'

androidTestApi('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nielsmasdorp.heartstone

import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.nielsmasdorp.flightapp", appContext.packageName)
}
}
25 changes: 25 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nielsmasdorp.heartstone">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name="com.nielsmasdorp.heartstone.HeartstoneApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="com.nielsmasdorp.heartstone.presentation.card.CardsActivity"
android:label="@string/cards_title">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
File renamed without changes.
25 changes: 25 additions & 0 deletions app/src/main/java/com/nielsmasdorp/heartstone/HeartstoneApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nielsmasdorp.heartstone

import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import timber.log.Timber

class HeartstoneApp : DaggerApplication() {

override fun onCreate() {
super.onCreate()
setupTimber()
}

override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerHeartstoneAppComponent.builder()
.heartstoneAppModule(HeartstoneAppModule())
.create(this)
}

private fun setupTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
Loading