master 7dcc3aa037c7 cached
111 files
167.7 KB
43.0k tokens
337 symbols
1 requests
Download .txt
Showing preview only (200K chars total). Download the full file or copy to clipboard to get everything.
Repository: dmilicic/android-clean-sample-app
Branch: master
Commit: 7dcc3aa037c7
Files: 111
Total size: 167.7 KB

Directory structure:
gitextract_jhfjo9q6/

├── .gitignore
├── .idea/
│   ├── .name
│   ├── codeStyleSettings.xml
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── QA/
│   ├── findbugs/
│   │   └── findbugs-filter.xml
│   └── quality.gradle
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── kodelabs/
│       │               └── mycosts/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── kodelabs/
│       │   │           └── mycosts/
│       │   │               ├── AndroidApplication.java
│       │   │               ├── domain/
│       │   │               │   ├── executor/
│       │   │               │   │   ├── Executor.java
│       │   │               │   │   ├── MainThread.java
│       │   │               │   │   └── impl/
│       │   │               │   │       └── ThreadExecutor.java
│       │   │               │   ├── interactors/
│       │   │               │   │   ├── AddCostInteractor.java
│       │   │               │   │   ├── DeleteCostInteractor.java
│       │   │               │   │   ├── EditCostInteractor.java
│       │   │               │   │   ├── GetAllCostsInteractor.java
│       │   │               │   │   ├── GetCostByIdInteractor.java
│       │   │               │   │   ├── base/
│       │   │               │   │   │   ├── AbstractInteractor.java
│       │   │               │   │   │   └── Interactor.java
│       │   │               │   │   └── impl/
│       │   │               │   │       ├── AddCostInteractorImpl.java
│       │   │               │   │       ├── DeleteCostInteractorImpl.java
│       │   │               │   │       ├── EditCostInteractorImpl.java
│       │   │               │   │       ├── GetAllCostsInteractorImpl.java
│       │   │               │   │       └── GetCostByIdInteractorImpl.java
│       │   │               │   ├── model/
│       │   │               │   │   └── Cost.java
│       │   │               │   └── repository/
│       │   │               │       └── CostRepository.java
│       │   │               ├── network/
│       │   │               │   ├── RestClient.java
│       │   │               │   ├── converters/
│       │   │               │   │   └── RESTModelConverter.java
│       │   │               │   ├── model/
│       │   │               │   │   ├── Payload.java
│       │   │               │   │   └── RESTCost.java
│       │   │               │   └── services/
│       │   │               │       └── SyncService.java
│       │   │               ├── presentation/
│       │   │               │   ├── animation/
│       │   │               │   │   └── AnimatorFactory.java
│       │   │               │   ├── converter/
│       │   │               │   │   └── DailyTotalCostConverter.java
│       │   │               │   ├── model/
│       │   │               │   │   └── DailyTotalCost.java
│       │   │               │   ├── presenters/
│       │   │               │   │   ├── AbstractPresenter.java
│       │   │               │   │   ├── AddCostPresenter.java
│       │   │               │   │   ├── BasePresenter.java
│       │   │               │   │   ├── EditCostPresenter.java
│       │   │               │   │   ├── MainPresenter.java
│       │   │               │   │   └── impl/
│       │   │               │   │       ├── AddCostPresenterImpl.java
│       │   │               │   │       ├── EditCostPresenterImpl.java
│       │   │               │   │       └── MainPresenterImpl.java
│       │   │               │   └── ui/
│       │   │               │       ├── BaseView.java
│       │   │               │       ├── activities/
│       │   │               │       │   ├── AboutActivity.java
│       │   │               │       │   ├── AbstractCostActivity.java
│       │   │               │       │   ├── AddCostActivity.java
│       │   │               │       │   ├── EditCostActivity.java
│       │   │               │       │   └── MainActivity.java
│       │   │               │       ├── adapters/
│       │   │               │       │   └── CostItemAdapter.java
│       │   │               │       ├── customviews/
│       │   │               │       │   ├── CostItemView.java
│       │   │               │       │   └── ExpandedCostView.java
│       │   │               │       ├── fragments/
│       │   │               │       │   └── DatePickerFragment.java
│       │   │               │       └── listeners/
│       │   │               │           ├── IndividualCostViewClickListener.java
│       │   │               │           └── RecyclerViewClickListener.java
│       │   │               ├── storage/
│       │   │               │   ├── CostRepositoryImpl.java
│       │   │               │   ├── contentprovider/
│       │   │               │   │   └── StubProvider.java
│       │   │               │   ├── converters/
│       │   │               │   │   └── StorageModelConverter.java
│       │   │               │   ├── database/
│       │   │               │   │   └── CostDatabase.java
│       │   │               │   └── model/
│       │   │               │       └── Cost.java
│       │   │               ├── sync/
│       │   │               │   ├── SyncAdapter.java
│       │   │               │   ├── SyncService.java
│       │   │               │   └── auth/
│       │   │               │       ├── Authenticator.java
│       │   │               │       ├── AuthenticatorService.java
│       │   │               │       └── DummyAccountProvider.java
│       │   │               ├── threading/
│       │   │               │   └── MainThreadImpl.java
│       │   │               └── utils/
│       │   │                   ├── AuthUtils.java
│       │   │                   └── DateUtils.java
│       │   └── res/
│       │       ├── anim/
│       │       │   └── hold.xml
│       │       ├── drawable/
│       │       │   └── rounded_corner.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_add_cost.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── card_daily_cost_item.xml
│       │       │   ├── card_expanded_daily_cost_item.xml
│       │       │   ├── content_about.xml
│       │       │   ├── content_add_cost.xml
│       │       │   ├── expanded_cost_item.xml
│       │       │   └── individual_cost_item.xml
│       │       ├── menu/
│       │       │   ├── menu_add_cost.xml
│       │       │   ├── menu_cost_item.xml
│       │       │   └── menu_main.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       ├── values-v21/
│       │       │   └── styles.xml
│       │       ├── values-w820dp/
│       │       │   └── dimens.xml
│       │       └── xml/
│       │           ├── authenticator.xml
│       │           └── syncadapter.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── kodelabs/
│                       └── mycosts/
│                           ├── domain/
│                           │   └── interactors/
│                           │       └── GetCostByIdTest.java
│                           ├── presentation/
│                           │   └── converter/
│                           │       └── DailyTotalCostConverterTest.java
│                           ├── threading/
│                           │   └── TestMainThread.java
│                           └── util/
│                               └── TestDateUtil.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
=======
# Built application files
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/
/*/build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log


================================================
FILE: .idea/.name
================================================
My Costs

================================================
FILE: .idea/codeStyleSettings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectCodeStyleSettingsManager">
    <option name="PER_PROJECT_SETTINGS">
      <value>
        <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
        <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
        <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
          <value />
        </option>
        <option name="IMPORT_LAYOUT_TABLE">
          <value>
            <package name="android" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="com" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="junit" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="net" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="org" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="java" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="javax" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="" withSubpackages="true" static="false" />
            <emptyLine />
            <package name="" withSubpackages="true" static="true" />
            <emptyLine />
          </value>
        </option>
        <option name="RIGHT_MARGIN" value="100" />
        <AndroidXmlCodeStyleSettings>
          <option name="USE_CUSTOM_SETTINGS" value="true" />
        </AndroidXmlCodeStyleSettings>
        <Objective-C-extensions>
          <option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
          <option name="RELEASE_STYLE" value="IVAR" />
          <option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
          <file>
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
          </file>
          <class>
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
            <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
          </class>
          <extensions>
            <pair source="cpp" header="h" />
            <pair source="c" header="h" />
          </extensions>
        </Objective-C-extensions>
        <XML>
          <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
        </XML>
        <codeStyleSettings language="XML">
          <option name="FORCE_REARRANGE_MODE" value="1" />
          <indentOptions>
            <option name="CONTINUATION_INDENT_SIZE" value="4" />
          </indentOptions>
          <arrangement>
            <rules>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>xmlns:android</NAME>
                      <XML_NAMESPACE>Namespace:</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>xmlns:.*</NAME>
                      <XML_NAMESPACE>Namespace:</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:id</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:name</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>name</NAME>
                      <XML_NAMESPACE>^$</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>style</NAME>
                      <XML_NAMESPACE>^$</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*</NAME>
                      <XML_NAMESPACE>^$</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:layout_width</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:layout_height</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:layout_.*</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:width</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*:height</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*</NAME>
                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
              <section>
                <rule>
                  <match>
                    <AND>
                      <NAME>.*</NAME>
                      <XML_NAMESPACE>.*</XML_NAMESPACE>
                    </AND>
                  </match>
                  <order>BY_NAME</order>
                </rule>
              </section>
            </rules>
          </arrangement>
        </codeStyleSettings>
      </value>
    </option>
    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Dario's" />
  </component>
</project>

================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <option name="DEFAULT_COMPILER" value="Javac" />
    <resourceExtensions />
    <wildcardResourcePatterns>
      <entry name="!?*.java" />
      <entry name="!?*.form" />
      <entry name="!?*.class" />
      <entry name="!?*.groovy" />
      <entry name="!?*.scala" />
      <entry name="!?*.flex" />
      <entry name="!?*.kt" />
      <entry name="!?*.clj" />
    </wildcardResourcePatterns>
    <annotationProcessing>
      <profile default="true" name="Default" enabled="false">
        <processorPath useClasspath="true" />
      </profile>
    </annotationProcessing>
  </component>
</project>

================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="" />
</component>

================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="distributionType" value="LOCAL" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.8" />
        <option name="gradleJvm" value="1.7" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="EntryPointsManager">
    <entry_points version="2.0" />
  </component>
  <component name="NullableNotNullManager">
    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
    <option name="myNullables">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
        </list>
      </value>
    </option>
    <option name="myNotNulls">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
        </list>
      </value>
    </option>
  </component>
  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
    <OptionsSetting value="true" id="Add" />
    <OptionsSetting value="true" id="Remove" />
    <OptionsSetting value="true" id="Checkout" />
    <OptionsSetting value="true" id="Update" />
    <OptionsSetting value="true" id="Status" />
    <OptionsSetting value="true" id="Edit" />
    <ConfirmationsSetting value="0" id="Add" />
    <ConfirmationsSetting value="0" id="Remove" />
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
  <component name="masterDetails">
    <states>
      <state key="GlobalLibrariesConfigurable.UI">
        <settings>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
      <state key="JdkListConfigurable.UI">
        <settings>
          <last-edited>Android SDK</last-edited>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
      <state key="ProjectJDKs.UI">
        <settings>
          <last-edited>Android API 21 Platform</last-edited>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
      <state key="ProjectLibrariesConfigurable.UI">
        <settings>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
      <state key="ScopeChooserConfigurable.UI">
        <settings>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
    </states>
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/MyCosts.iml" filepath="$PROJECT_DIR$/MyCosts.iml" />
      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 Dario Miličić

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: QA/findbugs/findbugs-filter.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <!-- ignore all issues in resource generation -->
        <Class name="~.*\.R\$.*" />
    </Match>
    <Match>
        <Class name="~.*\.Manifest\$.*" />
    </Match>

    <!-- ignore all bugs in test classes, except for those bugs specifically relating to JUnit tests -->
    <Match>
        <Class name="~.*\.*Test" />
        <!-- test classes are suffixed by 'Test' -->
        <Not>
            <Bug code="IJU" />
            <!-- 'IJU' is the code for bugs related to JUnit test code -->
        </Not>
    </Match>
</FindBugsFilter>

================================================
FILE: QA/quality.gradle
================================================
apply plugin: 'findbugs'

task findbugs(type: FindBugs) {
    ignoreFailures = true
    effort = "default"
    reportLevel = "medium"
    excludeFilter = new File("${project.rootDir}/QA/findbugs/findbugs-filter.xml")
    classes = files("${project.rootDir}/app/build/intermediates/classes")
    source = fileTree('src/main/java/')
    classpath = files()
    reports {
        xml.enabled = false
        html.enabled = true
        html {
            destination "${project.buildDir}/reports/findbugs/findbugs-output.html"
        }
    }
}

================================================
FILE: README.md
================================================
# Android Clean - Cost Tracker
A sample cost-tracker app that showcases my Clean architecture approach to build Android applications. It is a simple app with **core features** that include:

- Adding, editing and deleting a cost with a date, category, description and amount
- Displaying a list of summarized costs day by day
- Clicking on a summarized cost should display details of all costs for that day

That's it. For now.

You are free to download it, modify it, fork it and do anything you want with it.

## What is Clean Architecture?

In Clean, code is separated into layers in an onion shape. The outer layers of the onion depend on the inner layers but the opposite is not true. It can have an arbitrary amount of layers but for most applications there are 3 layers:

- Outer: Implementation layer
- Middle:  Presenter/Controller layer
- Inner: Business logic layer

The **implementation layer** is where everything framework specific happens. Framework specific code includes every line of code that is not solving your problem, this includes all Android stuff like creating activities and fragments, sending intents, and more general code like networking code and databases. The purpose of the **presenter/controller layer** is to act as a connector between your business logic and framework specific code.

The most important layer is the **business logic layer**. This is where you actually solve the problem you want to solve building your app. This layer does not contain any framework specific code and you should be able to run it without an emulator. This way you can have your business logic code that is easy to test, develop and maintain. **That is the main benefit of Clean Architecture.**

More general info about Clean Architecture can be found on this [blog]. This is a general explanation so let me explain how should it look like specifically in Android and how exactly do I build apps using Clean.

## How this app is structured

I've found that the most practical way to build Android apps with Clean is with the following approach. This is how this sample app is structured:

#### Outer layers
- The **presentation** layer has a standard [MVP] structure. All Activites and Fragments, everything view related and user-facing is put into the layer.
- Database specific code is inside the **storage** layer.
- Network specific code is inside the **network** layer.
- Any other framework specific code would be put into its own layer, for example in Android a **bluetooth** layer is something I often have.

#### Inner/Core layer
- Business logic is put into the **domain** layer.

Although I am omitting a middle layer, that is not actually true. Because my presentation layer actually includes **Presenters**, this provides a good separation of code between presentation and domain layers. Communication between layers is done using interfaces as explained in the blog linked above. In short, the inner layer only uses an interface while its the job of the outer layer to implement it. This way the inner layer only cares about calling methods on an interface, without actually knowing what is going on under the hood.

You can read more about it in my [detailed guide].

### Syncing to backend

There is a rails app I made that syncs all cost items to the server. You can find it here: https://mycosts-app.herokuapp.com/. This is a very simple app just to showcase the sync feature and its [source code] is open. Cost items are synced in real-time so you can see costs appearing on the website when you create them on Android. Editing and deleting are currently not supported.

## Future improvements

This list of future improvements for this app and is something I may or may not actively work on. If anyone wants to get into contributing to open source, these can be a great way to start and are relatively easy to implement. I would be happy if it helps you learn and would accept a pull request if any of these are implemented:

- There is only a list of daily costs, there should be a list for weekly, monthly and perhaps yearly.
- A graph detailing how much a user spends on each category would be very useful. *Hint: Maybe use [MPAndroidChart]*
- There is a lot of dependency injection going on, someone can reduce the amount of code by using a DI framework. *Hint: Dagger 2*
- Nothing is displayed on the main screen when there are no costs. *This is the easiest one, we just need to display something to tell users there is nothing to display, doh.*
- Add support for more than one currency.
- There is a fixed amount of categories, someone could add a way to manually add more categories.
- ???


License
----

MIT


[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)


[source code]: <https://github.com/dmilicic/mycosts-rails-backend>
[detailed guide]: <https://medium.com/@dmilicic/a-detailed-guide-on-developing-android-apps-using-the-clean-architecture-pattern-d38d71e94029>
[blog]: <https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html>
[MVP]: <https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter>
[MPAndroidChart]: <https://github.com/PhilJay/MPAndroidChart>
[DBFlow]: <https://github.com/Raizlabs/DBFlow>


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
apply from: "${project.rootDir}/QA/quality.gradle"

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.kodelabs.mycosts"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

def dbflow_version = "3.0.0-beta3"


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    // general
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.jakewharton:butterknife:7.0.1'
    compile 'com.jakewharton.timber:timber:4.1.0'

    // inspection
    compile 'com.facebook.stetho:stetho:1.3.0'
    compile 'com.facebook.stetho:stetho-okhttp3:1.3.0'

    // network
    compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
    compile 'com.squareup.retrofit2:converter-gson:+'
    compile 'com.squareup.okhttp3:logging-interceptor:3.0.1'

    // database
    apt "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}"
    compile "com.github.Raizlabs.DBFlow:dbflow-core:${dbflow_version}"
    compile "com.github.Raizlabs.DBFlow:dbflow:${dbflow_version}"

    // material design
    compile 'com.android.support:cardview-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
    compile('com.github.ozodrukh:CircularReveal:1.1.1@aar') {
        transitive = true;
    }

    // tests
    testCompile 'junit:junit:4.12'
    testCompile "org.mockito:mockito-core:1.+"
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/dmilicic/Documents/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# 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 *;
#}


================================================
FILE: app/src/androidTest/java/com/kodelabs/mycosts/ApplicationTest.java
================================================
package com.kodelabs.mycosts;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest
    package="com.kodelabs.mycosts"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <application
        android:name=".AndroidApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".presentation.ui.activities.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".presentation.ui.activities.AddCostActivity"
            android:label="Add cost"
            android:theme="@style/AppTheme" />
        <activity
            android:name=".presentation.ui.activities.EditCostActivity"
            android:label="Edit cost"
            android:theme="@style/AppTheme" />
        <activity
            android:name=".presentation.ui.activities.AboutActivity"
            android:label="@string/title_activity_about"
            android:parentActivityName=".presentation.ui.activities.MainActivity"
            android:theme="@style/AppTheme">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.kodelabs.mycosts.presentation.ui.activities.MainActivity" />
        </activity>


        <!-- Sync service section -->
        <service
            android:name=".sync.auth.AuthenticatorService"
            android:exported="false">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
        </service>
        <service
            android:name=".sync.SyncService"
            android:exported="false"
            android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>
            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter" />
        </service>

        <provider
            android:name=".storage.contentprovider.StubProvider"
            android:authorities="@string/stub_content_authority"
            android:exported="false"
            android:syncable="true" />
    </application>

</manifest>


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/AndroidApplication.java
================================================
package com.kodelabs.mycosts;

import android.app.Application;

import com.facebook.stetho.Stetho;
import com.raizlabs.android.dbflow.config.FlowManager;

import timber.log.Timber;
import timber.log.Timber.DebugTree;

/**
 * Created by dmilicic on 12/10/15.
 */
public class AndroidApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // init database
        FlowManager.init(this);

        // enable logging
        Timber.plant(new DebugTree());

        // enable stetho
        Stetho.initializeWithDefaults(this);
    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/Executor.java
================================================
package com.kodelabs.mycosts.domain.executor;

import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;

/**
 * This executor is responsible for running interactors on background threads.
 * <p/>
 * Created by dmilicic on 7/29/15.
 */
public interface Executor {

    /**
     * This method should call the interactor's run method and thus start the interactor. This should be called
     * on a background thread as interactors might do lengthy operations.
     *
     * @param interactor The interactor to run.
     */
    void execute(final AbstractInteractor interactor);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/MainThread.java
================================================
package com.kodelabs.mycosts.domain.executor;

/**
 * This interface will define a class that will enable interactors to run certain operations on the main (UI) thread. For example,
 * if an interactor needs to show an object to the UI this can be used to make sure the show method is called on the UI
 * thread.
 * <p/>
 * Created by dmilicic on 7/29/15.
 */
public interface MainThread {

    /**
     * Make runnable operation run in the main thread.
     *
     * @param runnable The runnable to run.
     */
    void post(final Runnable runnable);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/impl/ThreadExecutor.java
================================================
package com.kodelabs.mycosts.domain.executor.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * This singleton class will make sure that each interactor operation gets a background thread.
 * <p/>
 * Created by dmilicic on 7/29/15.
 */
public class ThreadExecutor implements Executor {

    // This is a singleton
    private static volatile ThreadExecutor sThreadExecutor;

    private static final int                     CORE_POOL_SIZE  = 3;
    private static final int                     MAX_POOL_SIZE   = 5;
    private static final int                     KEEP_ALIVE_TIME = 120;
    private static final TimeUnit                TIME_UNIT       = TimeUnit.SECONDS;
    private static final BlockingQueue<Runnable> WORK_QUEUE      = new LinkedBlockingQueue<Runnable>();

    private ThreadPoolExecutor mThreadPoolExecutor;

    private ThreadExecutor() {
        long keepAlive = KEEP_ALIVE_TIME;
        mThreadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                keepAlive,
                TIME_UNIT,
                WORK_QUEUE);
    }

    @Override
    public void execute(final AbstractInteractor interactor) {
        mThreadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                // run the main logic
                interactor.run();

                // mark it as finished
                interactor.onFinished();
            }
        });
    }

    /**
     * Returns a singleton instance of this executor. If the executor is not initialized then it initializes it and returns
     * the instance.
     */
    public static Executor getInstance() {
        if (sThreadExecutor == null) {
            sThreadExecutor = new ThreadExecutor();
        }

        return sThreadExecutor;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/AddCostInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.interactors.base.Interactor;

/**
 * Created by dmilicic on 12/23/15.
 */
public interface AddCostInteractor extends Interactor {

    interface Callback {
        void onCostAdded();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/DeleteCostInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.interactors.base.Interactor;
import com.kodelabs.mycosts.domain.model.Cost;

/**
 * Created by dmilicic on 12/26/15.
 */
public interface DeleteCostInteractor extends Interactor {

    interface Callback {
        void onCostDeleted(Cost cost);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/EditCostInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.interactors.base.Interactor;
import com.kodelabs.mycosts.domain.model.Cost;

/**
 * Created by dmilicic on 12/26/15.
 */
public interface EditCostInteractor extends Interactor {

    interface Callback {

        void onCostUpdated(Cost cost);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetAllCostsInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.interactors.base.Interactor;
import com.kodelabs.mycosts.domain.model.Cost;

import java.util.List;

/**
 * Created by dmilicic on 12/10/15.
 * <p/>
 * This interactor is responsible for retrieving a list of costs from the database.
 */
public interface GetAllCostsInteractor extends Interactor {

    interface Callback {
        void onCostsRetrieved(List<Cost> costList);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.interactors.base.Interactor;
import com.kodelabs.mycosts.domain.model.Cost;

/**
 * Created by dmilicic on 12/27/15.
 */
public interface GetCostByIdInteractor extends Interactor {

    interface Callback {
        void onCostRetrieved(Cost cost);

        void noCostFound();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/AbstractInteractor.java
================================================
package com.kodelabs.mycosts.domain.interactors.base;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;

/**
 * Created by dmilicic on 8/4/15.
 * <p/>
 * This abstract class implements some common methods for all interactors. Cancelling an interactor, check if its running
 * and finishing an interactor has mostly the same code throughout so that is why this class was created. Field methods
 * are declared volatile as we might use these methods from different threads (mainly from UI).
 * <p/>
 * For example, when an activity is getting destroyed then we should probably cancel an interactor
 * but the request will come from the UI thread unless the request was specifically assigned to a background thread.
 */
public abstract class AbstractInteractor implements Interactor {

    protected Executor   mThreadExecutor;
    protected MainThread mMainThread;

    protected volatile boolean mIsCanceled;
    protected volatile boolean mIsRunning;

    public AbstractInteractor(Executor threadExecutor, MainThread mainThread) {
        mThreadExecutor = threadExecutor;
        mMainThread = mainThread;
    }

    /**
     * This method contains the actual business logic of the interactor. It SHOULD NOT BE USED DIRECTLY but, instead, a
     * developer should call the execute() method of an interactor to make sure the operation is done on a background thread.
     * <p/>
     * This method should only be called directly while doing unit/integration tests. That is the only reason it is declared
     * public as to help with easier testing.
     */
    public abstract void run();

    public void cancel() {
        mIsCanceled = true;
        mIsRunning = false;
    }

    public boolean isRunning() {
        return mIsRunning;
    }

    public void onFinished() {
        mIsRunning = false;
        mIsCanceled = false;
    }

    public void execute() {

        // mark this interactor as running
        this.mIsRunning = true;

        // start running this interactor in a background thread
        mThreadExecutor.execute(this);
    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/Interactor.java
================================================
package com.kodelabs.mycosts.domain.interactors.base;

/**
 * Created by dmilicic on 12/13/15.
 */
public interface Interactor {

    /**
     * This is the main method that starts an interactor. It will make sure that the interactor operation is done on a
     * background thread.
     */
    void execute();
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/AddCostInteractorImpl.java
================================================
package com.kodelabs.mycosts.domain.interactors.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.AddCostInteractor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;

import java.util.Date;

/**
 * This interactor is responsible for creating and adding a new cost item into the database. It should get all the data needed to create
 * a new cost object and it should insert it in our repository.
 * <p/>
 * Created by dmilicic on 12/23/15.
 */
public class AddCostInteractorImpl extends AbstractInteractor implements AddCostInteractor {

    private AddCostInteractor.Callback mCallback;
    private CostRepository             mCostRepository;

    private String mCategory;
    private String mDescription;
    private Date   mDate;
    private double mAmount;

    public AddCostInteractorImpl(Executor threadExecutor, MainThread mainThread,
                                 Callback callback, CostRepository costRepository, String category,
                                 String description, Date date, double amount) {
        super(threadExecutor, mainThread);
        mCallback = callback;
        mCostRepository = costRepository;
        mCategory = category;
        mDescription = description;
        mDate = date;
        mAmount = amount;
    }

    @Override
    public void run() {
        // create a new cost object and insert it
        Cost cost = new Cost(mCategory, mDescription, mDate, mAmount);
        mCostRepository.insert(cost);

        // notify on the main thread that we have inserted this item
        mMainThread.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onCostAdded();
            }
        });

    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/DeleteCostInteractorImpl.java
================================================
package com.kodelabs.mycosts.domain.interactors.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.DeleteCostInteractor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;

/**
 * Interactor responsible for deleting a cost from the database.
 * <p/>
 * Created by dmilicic on 12/26/15.
 */
public class DeleteCostInteractorImpl extends AbstractInteractor implements DeleteCostInteractor {

    private long                          mCostId;
    private DeleteCostInteractor.Callback mCallback;
    private CostRepository                mCostRepository;

    public DeleteCostInteractorImpl(Executor threadExecutor,
                                    MainThread mainThread, long costId,
                                    Callback callback, CostRepository costRepository) {
        super(threadExecutor, mainThread);
        mCostId = costId;
        mCallback = callback;
        mCostRepository = costRepository;
    }

    @Override
    public void run() {

        // check if this object even exists
        final Cost cost = mCostRepository.getCostById(mCostId);

        // delete this cost item
        if (cost != null) mCostRepository.delete(cost);

        // notify on the main thread
        mMainThread.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onCostDeleted(cost);
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/EditCostInteractorImpl.java
================================================
package com.kodelabs.mycosts.domain.interactors.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.EditCostInteractor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;

import java.util.Date;

/**
 * This interactor handles editing a cost item. It should update it if it exists in the database, otherwise it should insert it.
 * <p/>
 * Created by dmilicic on 12/26/15.
 */
public class EditCostInteractorImpl extends AbstractInteractor implements EditCostInteractor {


    private EditCostInteractor.Callback mCallback;
    private CostRepository              mCostRepository;

    private Cost mUpdatedCost;

    private String mCategory;
    private String mDescription;
    private Date   mDate;
    private double mAmount;


    public EditCostInteractorImpl(Executor threadExecutor, MainThread mainThread,
                                  Callback callback, CostRepository costRepository,
                                  Cost updatedCost, String category, String description, Date date, double amount) {
        super(threadExecutor, mainThread);
        mCallback = callback;
        mCostRepository = costRepository;
        mUpdatedCost = updatedCost;
        mCategory = category;
        mDescription = description;
        mDate = date;
        mAmount = amount;
    }

    @Override
    public void run() {

        // check if it exists in the database
        long costId = mUpdatedCost.getId();
        Cost costToEdit = mCostRepository.getCostById(costId);

        // there is no item with this ID in the database, lets insert it
        if (costToEdit == null) {
            costToEdit = new Cost(mCategory, mDescription, mDate, mAmount);
            mCostRepository.insert(costToEdit);
        } else { // we found the item in the database, lets update it

            // update the cost
            costToEdit.setAmount(mAmount);
            costToEdit.setCategory(mCategory);
            costToEdit.setDate(mDate);
            costToEdit.setDescription(mDescription);

            // update in the db
            mCostRepository.update(costToEdit);
        }

        // notify on main thread that the update was successful
        mMainThread.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onCostUpdated(mUpdatedCost);
            }
        });

    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetAllCostsInteractorImpl.java
================================================
package com.kodelabs.mycosts.domain.interactors.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.GetAllCostsInteractor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * This interactor handles getting all costs from the database in a sorted manner. Costs should be sorted by date with
 * the most recent one coming first and the oldest one coming last.
 * <p/>
 * Created by dmilicic on 12/10/15.
 */
public class GetAllCostsInteractorImpl extends AbstractInteractor implements GetAllCostsInteractor {

    private Callback       mCallback;
    private CostRepository mCostRepository;

    private Comparator<Cost> mCostComparator = new Comparator<Cost>() {
        @Override
        public int compare(Cost lhs, Cost rhs) {

            if (lhs.getDate().before(rhs.getDate()))
                return 1;

            if (rhs.getDate().before(lhs.getDate()))
                return -1;

            return 0;
        }
    };

    public GetAllCostsInteractorImpl(Executor threadExecutor, MainThread mainThread, CostRepository costRepository,
                                     Callback callback) {
        super(threadExecutor, mainThread);

        if (costRepository == null || callback == null) {
            throw new IllegalArgumentException("Arguments can not be null!");
        }

        mCostRepository = costRepository;
        mCallback = callback;
    }

    @Override
    public void run() {
        // retrieve the costs from the database
        final List<Cost> costs = mCostRepository.getAllCosts();

        // sort them so the most recent cost items come first, and oldest comes last
        Collections.sort(costs, mCostComparator);

        // Show costs on the main thread
        mMainThread.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onCostsRetrieved(costs);
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetCostByIdInteractorImpl.java
================================================
package com.kodelabs.mycosts.domain.interactors.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.GetCostByIdInteractor;
import com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;

/**
 * Interactor responsible for getting a single cost item from the database using its ID. It should return the cost item
 * or notify if there isn't one.
 * <p/>
 * Created by dmilicic on 12/27/15.
 */
public class GetCostByIdInteractorImpl extends AbstractInteractor implements GetCostByIdInteractor {

    private long                           mCostId;
    private CostRepository                 mCostRepository;
    private GetCostByIdInteractor.Callback mCallback;


    public GetCostByIdInteractorImpl(Executor threadExecutor, MainThread mainThread, long costId,
                                     CostRepository costRepository,
                                     Callback callback) {
        super(threadExecutor, mainThread);
        mCostId = costId;
        mCostRepository = costRepository;
        mCallback = callback;
    }

    @Override
    public void run() {
        final Cost cost = mCostRepository.getCostById(mCostId);

        if (cost == null) { // we didn't find the cost we were looking for

            // notify this on the main thread
            mMainThread.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.noCostFound();
                }
            });
        } else { // we found it!

            // send it on the main thread
            mMainThread.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onCostRetrieved(cost);
                }
            });
        }
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/model/Cost.java
================================================
package com.kodelabs.mycosts.domain.model;

import java.util.Date;

/**
 * Created by dmilicic on 12/10/15.
 */
public class Cost {
    private long   mId;
    private String mCategory;
    private String mDescription;
    private Date   mDate;
    private double mAmount;

    public Cost(String category, String description, Date date, double amount) {

        // cost will be "uniquely" identified by the current timestamp
        mId = new Date().getTime();

        mCategory = category;
        mDescription = description;
        mDate = date;
        mAmount = amount;
    }


    /**
     * This constructor should be used when we are converting an already existing cost item to this POJO, so we already have
     * an id variable.
     */
    public Cost(String category, String description, Date date, double amount, long id) {
        mId = id;
        mCategory = category;
        mDescription = description;
        mDate = date;
        mAmount = amount;
    }

    public void setCategory(String category) {
        mCategory = category;
    }

    public void setDescription(String description) {
        mDescription = description;
    }

    public void setDate(Date date) {
        mDate = date;
    }

    public void setAmount(double amount) {
        mAmount = amount;
    }

    public long getId() {
        return mId;
    }

    public String getCategory() {
        return mCategory;
    }

    public String getDescription() {
        return mDescription;
    }

    public Date getDate() {
        return mDate;
    }

    public double getAmount() {
        return mAmount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Cost cost = (Cost) o;

        return mId == cost.mId;

    }

    @Override
    public int hashCode() {
        return (int) (mId ^ (mId >>> 32));
    }

    @Override
    public String toString() {
        return "Cost{" +
                "mId=" + mId +
                ", mCategory='" + mCategory + '\'' +
                ", mDescription='" + mDescription + '\'' +
                ", mDate=" + mDate +
                ", mAmount=" + mAmount +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/domain/repository/CostRepository.java
================================================
package com.kodelabs.mycosts.domain.repository;

import com.kodelabs.mycosts.domain.model.Cost;

import java.util.List;

/**
 * Created by dmilicic on 12/13/15.
 */
public interface CostRepository {

    void insert(Cost cost);

    void update(Cost cost);

    Cost getCostById(long id);

    List<Cost> getAllCosts();

    List<Cost> getAllUnsyncedCosts();

    void markSynced(List<Cost> costs);

    void delete(Cost cost);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/network/RestClient.java
================================================
package com.kodelabs.mycosts.network;

import com.facebook.stetho.okhttp3.StethoInterceptor;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * <p/>
 * This is the main entry point for network communication. Use this class for instancing REST services which do the
 * actual communication.
 */
public class RestClient {

    /**
     * This is our main backend/server URL.
     */
    public static final String REST_API_URL = "https://mycosts-app.herokuapp.com/";
//    public static final String REST_API_URL = "http://192.168.0.12:3000";


    private static Retrofit s_retrofit;

    static {

        // enable logging
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .addNetworkInterceptor(new StethoInterceptor())
                .build();

        s_retrofit = new Retrofit.Builder()
                .baseUrl(REST_API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();
    }

    public static <T> T getService(Class<T> serviceClass) {
        return s_retrofit.create(serviceClass);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/network/converters/RESTModelConverter.java
================================================
package com.kodelabs.mycosts.network.converters;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.network.model.RESTCost;

import java.util.Date;

/**
 * Created by dmilicic on 2/14/16.
 */
public class RESTModelConverter {

    public static RESTCost convertToRestModel(Cost cost) {

        String desc = cost.getDescription();
        double amount = cost.getAmount();
        String category = cost.getCategory();
        Date date = cost.getDate();
        long id = cost.getId();

        return new RESTCost(id, category, desc, date, amount);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/network/model/Payload.java
================================================
package com.kodelabs.mycosts.network.model;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by dmilicic on 2/14/16.
 */
public class Payload {

    @SerializedName("user")
    private String mUsername;

    @SerializedName("costs")
    private List<RESTCost> mCosts;

    public Payload(String username) {
        mUsername = username;
        mCosts = new ArrayList<>();
    }

    public String getUsername() {
        return mUsername;
    }

    public List<RESTCost> getCosts() {
        return mCosts;
    }

    public void addCost(RESTCost cost) {
        mCosts.add(cost);
    }

    public static void main(String[] args) {
        Gson gson = new Gson();

        RESTCost cost = new RESTCost(100, "category", "desc", new Date(), 100.0);
        String username = "user";

        Payload data = new Payload(username);
        data.addCost(cost);

        cost = new RESTCost(200, "category", "desc", new Date(), 100.0);
        data.addCost(cost);

        cost = new RESTCost(300, "category", "desc", new Date(), 100.0);
        data.addCost(cost);

        String json = gson.toJson(data);

        System.out.println(json);
    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/network/model/RESTCost.java
================================================
package com.kodelabs.mycosts.network.model;

import com.google.gson.annotations.SerializedName;

import java.util.Date;

/**
 * Created by dmilicic on 2/14/16.
 */
public class RESTCost {

    @SerializedName("id")
    private long mId;

    @SerializedName("category")
    private String mCategory;

    @SerializedName("description")
    private String mDescription;

    @SerializedName("date")
    private Date mDate;

    @SerializedName("amount")
    private double mAmount;


    public RESTCost(long id, String category, String description, Date date, double amount) {
        mId = id;
        mCategory = category;
        mDescription = description;
        mDate = date;
        mAmount = amount;
    }

    public long getId() {
        return mId;
    }

    public String getCategory() {
        return mCategory;
    }

    public String getDescription() {
        return mDescription;
    }

    public Date getDate() {
        return mDate;
    }

    public double getAmount() {
        return mAmount;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/network/services/SyncService.java
================================================
package com.kodelabs.mycosts.network.services;

import com.kodelabs.mycosts.network.model.Payload;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;

/**
 * A REST interface describing how data will be synced with the backend.
 * <p/>
 */
public interface SyncService {

    /**
     * This endpoint will be used to send new costs created on this device.
     */
    @Headers("Connection: close")
    @POST("/costs")
    Call<Void> uploadData(@Body Payload data);
}

================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/animation/AnimatorFactory.java
================================================
package com.kodelabs.mycosts.presentation.animation;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.app.Activity;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;

import com.kodelabs.mycosts.R;

import io.codetail.animation.SupportAnimator;

/**
 * Created by dmilicic on 1/7/16.
 */
public class AnimatorFactory {

    public static final int REVEAL_ANIMATION_LENGTH = 350; // in milliseconds

    /**
     * Creates a circural reveal animation from a given source view. While revealing it uses the reveal layout and after
     * the animation completes, it starts the activity given in the intent.
     *
     * @param src          The source view from which the circular animation starts.
     * @param revealLayout The layout to reveal in the animation.
     * @param intent       The intent used to start another activity.
     * @param activity     The activity is needed as a context object.
     */
    public static void enterReveal(ViewGroup revealLayout, final Intent intent, final Activity activity) {

        int cx = (revealLayout.getLeft() + revealLayout.getRight());
        int cy = revealLayout.getTop();
        int finalRadius = Math.max(revealLayout.getWidth(), revealLayout.getHeight());

        AnimatorListener animatorListener = new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                activity.startActivity(intent);
                activity.overridePendingTransition(0, R.anim.hold);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        };


//        src.setVisibility(View.INVISIBLE);

        // make the view visible and start the animation
        revealLayout.setVisibility(View.VISIBLE);

        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {

            Animator anim =
                    ViewAnimationUtils.createCircularReveal(revealLayout, cx, cy, 0, finalRadius);

            anim.setDuration(REVEAL_ANIMATION_LENGTH);
            anim.addListener(animatorListener);
            anim.start();
        } else {
            // create the animator for this view (the start radius is zero)
            SupportAnimator anim =
                    io.codetail.animation.ViewAnimationUtils.createCircularReveal(revealLayout, cx, cy, 0, finalRadius);

            anim.setDuration(REVEAL_ANIMATION_LENGTH);
            anim.addListener(new SupportAnimator.AnimatorListener() {
                @Override
                public void onAnimationStart() {

                }

                @Override
                public void onAnimationEnd() {
                    activity.startActivity(intent);
                    activity.overridePendingTransition(0, R.anim.hold);
                }

                @Override
                public void onAnimationCancel() {

                }

                @Override
                public void onAnimationRepeat() {

                }
            });
            anim.start();
        }
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverter.java
================================================
package com.kodelabs.mycosts.presentation.converter;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by dmilicic on 1/4/16.
 */
public class DailyTotalCostConverter {

    public static List<DailyTotalCost> convertCostsToDailyCosts(List<Cost> costList) {

        List<DailyTotalCost> result = new ArrayList<>();

        if (costList == null || costList.size() == 0) {
            // return an empty array if we have nothing to convert
            return result;
        }

        // declare and initialize data vars
        List<Cost> dailyCosts = new ArrayList<>();
        Date currentDate = costList.get(0).getDate();
        Cost cost;

        // iterate over all cost items received
        for (int idx = 0; idx < costList.size(); idx++) {

            cost = costList.get(idx);

            if (idx == 0) { // in case this is the first element

                // initialize the process
                dailyCosts = new ArrayList<>();
                currentDate = cost.getDate();
            }

            // add the current element to the list of daily costs - for the current date
            dailyCosts.add(cost);

            // check if this is the last element
            if (idx == costList.size() - 1) {
                // create a new daily total match
                DailyTotalCost dailyTotalCost = new DailyTotalCost(dailyCosts, currentDate);
                result.add(dailyTotalCost);

                continue;
            }

            // get the next element
            Cost nextCost = costList.get(idx + 1);

            // check if the next element is from a different day
            if (!nextCost.getDate().equals(currentDate)) {
                // create a new daily total match
                DailyTotalCost dailyTotalCost = new DailyTotalCost(dailyCosts, currentDate);
                result.add(dailyTotalCost);

                // repeat the process with the next item
                dailyCosts = new ArrayList<>();
                currentDate = nextCost.getDate();
            }
        }

        return result;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/model/DailyTotalCost.java
================================================
package com.kodelabs.mycosts.presentation.model;

import com.kodelabs.mycosts.domain.model.Cost;

import java.util.Date;
import java.util.List;

/**
 * Created by dmilicic on 1/4/16.
 */
public class DailyTotalCost {

    private List<Cost> mCostList;

    private Date   mDate;
    private double mTotalCost;

    public DailyTotalCost(List<Cost> costList, Date date) {
        mCostList = costList;
        mDate = date;

        // eagerly calculate the total cost
        mTotalCost = 0.0;
        for (int idx = 0; idx < costList.size(); idx++) {
            mTotalCost += costList.get(idx).getAmount();
        }
    }

    public List<Cost> getCostList() {
        return mCostList;
    }

    public Date getDate() {
        return mDate;
    }

    public double getTotalCost() {
        return mTotalCost;
    }

    @Override
    public String toString() {
        return "DailyTotalCost{" +
                "mCostList=" + mCostList +
                ", mDate=" + mDate +
                ", mTotalCost=" + mTotalCost +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AbstractPresenter.java
================================================
package com.kodelabs.mycosts.presentation.presenters;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;

/**
 * Created by dmilicic on 12/23/15.
 */
public abstract class AbstractPresenter {
    protected Executor   mExecutor;
    protected MainThread mMainThread;

    public AbstractPresenter(Executor executor, MainThread mainThread) {
        mExecutor = executor;
        mMainThread = mainThread;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AddCostPresenter.java
================================================
package com.kodelabs.mycosts.presentation.presenters;

import com.kodelabs.mycosts.presentation.ui.BaseView;

import java.util.Date;

/**
 * Created by dmilicic on 12/21/15.
 */
public interface AddCostPresenter extends BasePresenter {


    interface View extends BaseView {

        void onCostAdded();
    }

    void addNewCost(Date date, double amount, String description, String category);

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/BasePresenter.java
================================================
package com.kodelabs.mycosts.presentation.presenters;

/**
 * Created by dmilicic on 7/28/15.
 */
public interface BasePresenter {
    /**
     * Method that control the lifecycle of the view. It should be called in the view's
     * (Activity or Fragment) onResume() method.
     */
    void resume();

    /**
     * Method that controls the lifecycle of the view. It should be called in the view's
     * (Activity or Fragment) onPause() method.
     */
    void pause();

    /**
     * Method that controls the lifecycle of the view. It should be called in the view's
     * (Activity or Fragment) onStop() method.
     */
    void stop();

    /**
     * Method that control the lifecycle of the view. It should be called in the view's
     * (Activity or Fragment) onDestroy() method.
     */
    void destroy();


    /**
     * Method that should signal the appropriate view to show the appropriate error with the provided message.
     */
    void onError(String message);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/EditCostPresenter.java
================================================
package com.kodelabs.mycosts.presentation.presenters;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.ui.BaseView;

import java.util.Date;

/**
 * Created by dmilicic on 12/27/15.
 */
public interface EditCostPresenter {

    interface View extends BaseView {

        void onCostRetrieved(Cost cost);

        void onCostUpdated(Cost cost);
    }

    void getCostById(long id);

    void editCost(Cost cost, Date date, double amount, String description, String category);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/MainPresenter.java
================================================
package com.kodelabs.mycosts.presentation.presenters;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.presentation.ui.BaseView;

import java.util.List;

/**
 * Created by dmilicic on 12/10/15.
 */
public interface MainPresenter extends BasePresenter {

    interface View extends BaseView {

        void showCosts(List<DailyTotalCost> costs);

        void onClickDeleteCost(long costId);

        void onClickEditCost(long costId, int position);

        void onCostDeleted(Cost cost);
    }

    void getAllCosts();

    void deleteCost(long costId);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/AddCostPresenterImpl.java
================================================
package com.kodelabs.mycosts.presentation.presenters.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.AddCostInteractor;
import com.kodelabs.mycosts.domain.interactors.impl.AddCostInteractorImpl;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.presentation.presenters.AbstractPresenter;
import com.kodelabs.mycosts.presentation.presenters.AddCostPresenter;

import java.util.Date;

/**
 * Created by dmilicic on 12/23/15.
 */
public class AddCostPresenterImpl extends AbstractPresenter implements AddCostPresenter,
        AddCostInteractor.Callback {

    private AddCostPresenter.View mView;
    private CostRepository        mCostRepository;

    public AddCostPresenterImpl(Executor executor, MainThread mainThread,
                                View view, CostRepository costRepository) {
        super(executor, mainThread);
        mView = view;
        mCostRepository = costRepository;
    }

    @Override
    public void addNewCost(Date date, double amount, String description, String category) {
        AddCostInteractor addCostInteractor = new AddCostInteractorImpl(mExecutor,
                mMainThread,
                this,
                mCostRepository,
                category,
                description,
                date,
                amount);
        addCostInteractor.execute();
    }

    @Override
    public void onCostAdded() {
        mView.onCostAdded();
    }


    @Override
    public void resume() {

    }

    @Override
    public void pause() {

    }

    @Override
    public void stop() {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void onError(String message) {

    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/EditCostPresenterImpl.java
================================================
package com.kodelabs.mycosts.presentation.presenters.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.EditCostInteractor;
import com.kodelabs.mycosts.domain.interactors.GetCostByIdInteractor;
import com.kodelabs.mycosts.domain.interactors.impl.EditCostInteractorImpl;
import com.kodelabs.mycosts.domain.interactors.impl.GetCostByIdInteractorImpl;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.presentation.presenters.AbstractPresenter;
import com.kodelabs.mycosts.presentation.presenters.EditCostPresenter;

import java.util.Date;

/**
 * Created by dmilicic on 12/27/15.
 */
public class EditCostPresenterImpl extends AbstractPresenter
        implements EditCostPresenter, GetCostByIdInteractor.Callback, EditCostInteractor.Callback {

    private EditCostPresenter.View mView;
    private CostRepository         mCostRepository;

    public EditCostPresenterImpl(Executor executor, MainThread mainThread,
                                 View view, CostRepository costRepository) {
        super(executor, mainThread);
        mView = view;
        mCostRepository = costRepository;
    }

    @Override
    public void getCostById(long id) {
        GetCostByIdInteractor getCostByIdInteractor = new GetCostByIdInteractorImpl(
                mExecutor,
                mMainThread,
                id,
                mCostRepository,
                this
        );
        getCostByIdInteractor.execute();
    }


    @Override
    public void onCostRetrieved(Cost cost) {
        mView.onCostRetrieved(cost);
    }

    @Override
    public void noCostFound() {
        mView.showError("No cost found :(");
    }

    @Override
    public void editCost(Cost cost, Date date, double amount, String description, String category) {
        EditCostInteractor editCostInteractor = new EditCostInteractorImpl(
                mExecutor,
                mMainThread,
                this,
                mCostRepository,
                cost,
                category, description, date, amount
        );
        editCostInteractor.execute();
    }

    @Override
    public void onCostUpdated(Cost cost) {
        mView.onCostUpdated(cost);
    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/MainPresenterImpl.java
================================================
package com.kodelabs.mycosts.presentation.presenters.impl;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.DeleteCostInteractor;
import com.kodelabs.mycosts.domain.interactors.GetAllCostsInteractor;
import com.kodelabs.mycosts.domain.interactors.impl.DeleteCostInteractorImpl;
import com.kodelabs.mycosts.domain.interactors.impl.GetAllCostsInteractorImpl;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.presentation.converter.DailyTotalCostConverter;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.presentation.presenters.AbstractPresenter;
import com.kodelabs.mycosts.presentation.presenters.MainPresenter;

import java.util.List;

/**
 * Created by dmilicic on 12/13/15.
 */
public class MainPresenterImpl extends AbstractPresenter implements MainPresenter,
        GetAllCostsInteractor.Callback,
        DeleteCostInteractor.Callback {

    private MainPresenter.View mView;
    private CostRepository     mCostRepository;

    public MainPresenterImpl(Executor executor, MainThread mainThread,
                             View view, CostRepository costRepository) {
        super(executor, mainThread);
        mView = view;
        mCostRepository = costRepository;
    }

    @Override
    public void resume() {
        getAllCosts();
    }

    @Override
    public void pause() {

    }

    @Override
    public void stop() {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void onError(String message) {

    }

    @Override
    public void getAllCosts() {
        // get all costs
        GetAllCostsInteractor getCostsInteractor = new GetAllCostsInteractorImpl(
                mExecutor,
                mMainThread,
                mCostRepository,
                this
        );
        getCostsInteractor.execute();
    }

    @Override
    public void onCostsRetrieved(List<Cost> costList) {
        List<DailyTotalCost> dailyTotalCosts = DailyTotalCostConverter.convertCostsToDailyCosts(costList);
        mView.showCosts(dailyTotalCosts);
    }

    @Override
    public void deleteCost(long costId) {

        // delete this cost item in a background thread
        DeleteCostInteractor deleteCostInteractor = new DeleteCostInteractorImpl(
                mExecutor,
                mMainThread,
                costId,
                this,
                mCostRepository
        );
        deleteCostInteractor.execute();
    }


    @Override
    public void onCostDeleted(Cost cost) {
        mView.onCostDeleted(cost);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/BaseView.java
================================================
package com.kodelabs.mycosts.presentation.ui;

/**
 * Created by dmilicic on 7/28/15.
 * <p/>
 * This interface represents a basic view. All views should implement these common methods.
 */
public interface BaseView {

    /**
     * This is a general method used for showing some kind of progress during a background task. For example, this
     * method should show a progress bar and/or disable buttons before some background work starts.
     */
    void showProgress();

    /**
     * This is a general method used for hiding progress information after a background task finishes.
     */
    void hideProgress();

    /**
     * This method is used for showing error messages on the UI.
     *
     * @param message The error message to be dislayed.
     */
    void showError(String message);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AboutActivity.java
================================================
package com.kodelabs.mycosts.presentation.ui.activities;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

import com.kodelabs.mycosts.R;

public class AboutActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_about);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // let the user choose his email client
                Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
                        "mailto", "dario.milicic@gmail.com", null));
                startActivity(Intent.createChooser(emailIntent, "Send email..."));
            }
        });
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AbstractCostActivity.java
================================================
package com.kodelabs.mycosts.presentation.ui.activities;

import android.app.DatePickerDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.presentation.ui.fragments.DatePickerFragment;
import com.kodelabs.mycosts.utils.DateUtils;

import java.util.Date;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.codetail.widget.RevealFrameLayout;

/**
 * Created by dmilicic on 12/26/15.
 */
public abstract class AbstractCostActivity extends AppCompatActivity
        implements DatePickerDialog.OnDateSetListener {


    @Bind(R.id.reveal_layout)
    RevealFrameLayout mRevealLayout;

    @Bind(R.id.toolbar)
    Toolbar mToolbar;

    @Bind(R.id.input_date)
    TextView mDateTextView;

    @Bind(R.id.input_amount)
    EditText mAmountEditText;

    @Bind(R.id.input_description)
    EditText mDescriptionEditText;

    @Bind(R.id.input_cost_category)
    Spinner mCategorySpinner;

    protected Date   mSelectedDate;
    protected String mDescription;
    protected String mCategory;
    protected double mAmount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_cost);
        ButterKnife.bind(this);

        mToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
        setSupportActionBar(mToolbar);
        mToolbar.setNavigationOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });


        mRevealLayout.setVisibility(View.VISIBLE);
    }

    @OnClick(R.id.input_date)
    public void showDatePickerDialog(View v) {
        DatePickerFragment newFragment = new DatePickerFragment();
        newFragment.setListener(this);
        newFragment.show(getFragmentManager(), "datePicker");
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_add_cost, menu);
        return true;
    }

    protected void extractFormData() {
        // extract data from the form
        try {
            mAmount = Double.valueOf(mAmountEditText.getText().toString());
        } catch (NumberFormatException e) {
            mAmount = 0.0;
        }

        // extract description and category
        mDescription = mDescriptionEditText.getText().toString();
        mCategory = mCategorySpinner.getSelectedItem().toString();
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        mSelectedDate = DateUtils.createDate(year, monthOfYear, dayOfMonth);
        mDateTextView.setText(DateUtils.formatDate(mSelectedDate));
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AddCostActivity.java
================================================
package com.kodelabs.mycosts.presentation.ui.activities;

import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.executor.impl.ThreadExecutor;
import com.kodelabs.mycosts.presentation.presenters.AddCostPresenter;
import com.kodelabs.mycosts.presentation.presenters.impl.AddCostPresenterImpl;
import com.kodelabs.mycosts.storage.CostRepositoryImpl;
import com.kodelabs.mycosts.threading.MainThreadImpl;
import com.kodelabs.mycosts.utils.DateUtils;

public class AddCostActivity extends AbstractCostActivity
        implements AddCostPresenter.View {

    private AddCostPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // setup the presenter
        mPresenter = new AddCostPresenterImpl(
                ThreadExecutor.getInstance(),
                MainThreadImpl.getInstance(),
                this,
                new CostRepositoryImpl(this)
        );
    }

    @Override
    protected void onResume() {
        super.onResume();

        // default day should be today
        mSelectedDate = DateUtils.getToday();
        mDateTextView.setText(DateUtils.formatDate(mSelectedDate));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_save) {

            extractFormData();

            // pass the data onto the presenter
            mPresenter.addNewCost(mSelectedDate, mAmount, mDescription, mCategory);
            return true;
        }

        return super.onOptionsItemSelected(item);

    }

    @Override
    public void onCostAdded() {
        Toast.makeText(this, "Saved!", Toast.LENGTH_LONG).show();
        onBackPressed();
    }

    @Override
    public void showProgress() {

    }

    @Override
    public void hideProgress() {

    }

    @Override
    public void showError(String message) {

    }

}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/EditCostActivity.java
================================================
package com.kodelabs.mycosts.presentation.ui.activities;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.MenuItem;
import android.widget.Toast;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.executor.impl.ThreadExecutor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.presenters.EditCostPresenter;
import com.kodelabs.mycosts.presentation.presenters.impl.EditCostPresenterImpl;
import com.kodelabs.mycosts.storage.CostRepositoryImpl;
import com.kodelabs.mycosts.threading.MainThreadImpl;
import com.kodelabs.mycosts.utils.DateUtils;

/**
 * Created by dmilicic on 12/27/15.
 */
public class EditCostActivity extends AbstractCostActivity implements EditCostPresenter.View {

    private EditCostPresenter mPresenter;
    private Cost              mEditedCost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // create a presenter for this screen
        mPresenter = new EditCostPresenterImpl(
                ThreadExecutor.getInstance(),
                MainThreadImpl.getInstance(),
                this,
                new CostRepositoryImpl(this)
        );

        // extract the cost id of the item we want to edit
        long costId = getIntent().getLongExtra(MainActivity.EXTRA_COST_ID, -1);

        // in case cost id is not sent
        if (costId == -1) {
            Toast.makeText(this, "Cost not found!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        // first get the old cost from the database
        mPresenter.getCostById(costId);
    }

    @Override
    public void onCostRetrieved(@NonNull Cost cost) {

        mEditedCost = cost;

        // populate the member variables
        mAmount = cost.getAmount();
        mSelectedDate = cost.getDate();
        mCategory = cost.getCategory();
        mDescription = cost.getDescription();

        prepopulateFields();
    }


    /**
     * Finds the position of the given category inside the spinner.
     *
     * @param category The provided category for which we are finding the position.
     * @return Returns an int which represents a position of the provided category in the spinner.
     */
    private int findCategoryPosition(String category) {
        int result = -1;
        String[] categoryArray = getResources().getStringArray(R.array.category_array);
        for (int i = 0; i < categoryArray.length; i++) {
            if (category.equals(categoryArray[i])) result = i;
        }

        return result;
    }

    private void prepopulateFields() {

        // prepopulate all text views
        mAmountEditText.setText(String.valueOf(mAmount));
        mDateTextView.setText(DateUtils.formatDate(mSelectedDate));
        mDescriptionEditText.setText(mDescription);

        // find the position of the category item to select
        int position = findCategoryPosition(mCategory);
        mCategorySpinner.setSelection(position);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_save) {

            extractFormData();

            // pass the data onto the presenter
            mPresenter.editCost(mEditedCost, mSelectedDate, mAmount, mDescription, mCategory);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }


    @Override
    public void onCostUpdated(Cost cost) {

        // build the data to send
        Intent data = new Intent();
        data.putExtra(MainActivity.EXTRA_COST_ID, cost.getId());

        // mark that this was a success
        setResult(RESULT_OK, data);
        finish();
    }

    @Override
    public void showProgress() {

    }

    @Override
    public void hideProgress() {

    }

    @Override
    public void showError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/MainActivity.java
================================================
package com.kodelabs.mycosts.presentation.ui.activities;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.executor.impl.ThreadExecutor;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.animation.AnimatorFactory;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.presentation.presenters.MainPresenter;
import com.kodelabs.mycosts.presentation.presenters.impl.MainPresenterImpl;
import com.kodelabs.mycosts.presentation.ui.adapters.CostItemAdapter;
import com.kodelabs.mycosts.storage.CostRepositoryImpl;
import com.kodelabs.mycosts.sync.auth.DummyAccountProvider;
import com.kodelabs.mycosts.threading.MainThreadImpl;

import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;
import io.codetail.widget.RevealFrameLayout;
import timber.log.Timber;

public class MainActivity extends AppCompatActivity implements MainPresenter.View {

    public static final String EXTRA_COST_ID = "extra_cost_id_key";

    public static final int EDIT_COST_REQUEST = 0;

    @Bind(R.id.expenses_list)
    RecyclerView mRecyclerView;

    @Bind(R.id.reveal_layout)
    RevealFrameLayout mRevealLayout;

    private MainPresenter mMainPresenter;

    private CostItemAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Timber.w("ONCREATE");

        init();
    }

    private void init() {

        // setup recycler view adapter
        mAdapter = new CostItemAdapter(this, this);

        // setup recycler view
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mAdapter);

        // setup toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // instantiate the presenter
        mMainPresenter = new MainPresenterImpl(
                ThreadExecutor.getInstance(),
                MainThreadImpl.getInstance(),
                this,
                new CostRepositoryImpl(this)
        );


        // create a dummy account if it doesn't yet exist
        DummyAccountProvider.CreateSyncAccount(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mMainPresenter.resume();

        // reset the layout
        mRevealLayout.setVisibility(View.INVISIBLE);

        Timber.w("ONRESUME");
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        switch (id) {
            case R.id.action_add_cost:
                // intent to start another activity
                final Intent intent = new Intent(MainActivity.this, AddCostActivity.class);

                // do the animation
                AnimatorFactory.enterReveal(mRevealLayout, intent, MainActivity.this);

                break;
            case R.id.action_about:
                final Intent aboutIntent = new Intent(MainActivity.this, AboutActivity.class);
                startActivity(aboutIntent);
                break;
            default:
                break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // check if everything is ok
        if (requestCode == EDIT_COST_REQUEST && resultCode == RESULT_OK) {

            // let the user know the edit succeded
            Toast.makeText(this, "Updated!", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void showCosts(List<DailyTotalCost> costs) {
        // signal the adapter that it has data to show
        mAdapter.addNewCosts(costs);
    }

    @Override
    public void onClickDeleteCost(final long costId) {
        DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case DialogInterface.BUTTON_POSITIVE:
                        mMainPresenter.deleteCost(costId);
                        break;

                    case DialogInterface.BUTTON_NEGATIVE:
                        //No button clicked
                        break;
                }
            }
        };

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("Delete this cost?")
                .setPositiveButton("Yes", dialogClickListener)
                .setNegativeButton("No", dialogClickListener)
                .show();
    }

    @Override
    public void onCostDeleted(Cost cost) {
        // we deleted some data, RELOAD ALL THE THINGS!
        mMainPresenter.getAllCosts();
    }

    @Override
    public void onClickEditCost(long costId, int position) {

        // intent to start another activity
        final Intent intent = new Intent(MainActivity.this, EditCostActivity.class);
        intent.putExtra(EXTRA_COST_ID, costId);

        startActivityForResult(intent, EDIT_COST_REQUEST);
    }

    @Override
    public void showProgress() {

    }

    @Override
    public void hideProgress() {

    }

    @Override
    public void showError(String message) {
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/adapters/CostItemAdapter.java
================================================
package com.kodelabs.mycosts.presentation.ui.adapters;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.presentation.presenters.MainPresenter;
import com.kodelabs.mycosts.presentation.ui.customviews.ExpandedCostView;
import com.kodelabs.mycosts.presentation.ui.listeners.IndividualCostViewClickListener;
import com.kodelabs.mycosts.presentation.ui.listeners.RecyclerViewClickListener;
import com.kodelabs.mycosts.utils.DateUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import butterknife.Bind;
import butterknife.ButterKnife;

/**
 * Created by dmilicic on 12/13/15.
 */
public class CostItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements RecyclerViewClickListener {


    private enum ViewType {
        CONTRACTED_CARD, EXPANDED_CARD
    }

    private List<DailyTotalCost> mCostList;
    private Context              mContext;

    private Set<Integer> mSelectedItems;

    public final MainPresenter.View mView;

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        @Bind(R.id.cost_item_title)
        public TextView mTitle;

        @Bind(R.id.cost_item_total_value)
        public TextView mTotalCost;

        private RecyclerViewClickListener mListener;

        public void setup(DailyTotalCost dailyTotalCost) {
            Context context = mTitle.getContext();

            final String dateText = DateUtils.dateToText(context, dailyTotalCost.getDate());
            final String title = String.format(context.getString(R.string.total_expenses), dateText);
            mTitle.setText(title);
            mTotalCost.setText(String.valueOf(dailyTotalCost.getTotalCost()) + "$");
        }

        @Override
        public void onClick(View v) {
            mListener.onClickView(getAdapterPosition());
        }

        public ViewHolder(View v, final RecyclerViewClickListener listener) {
            super(v);
            ButterKnife.bind(this, v);
            v.setOnClickListener(this);
            mListener = listener;
        }
    }

    public static class ExpandedViewHolder extends RecyclerView.ViewHolder
            implements View.OnClickListener, IndividualCostViewClickListener {

        @Bind(R.id.card_expanded_costview)
        public ExpandedCostView mExpandedCostView;

        private RecyclerViewClickListener mListener;

        @Override
        public void onClickDelete(long costId) {
            mListener.onClickDelete(getAdapterPosition(), costId);
        }

        @Override
        public void onClickEdit(long costId) {
            mListener.onClickEdit(getAdapterPosition(), costId);
        }

        @Override
        public void onClick(View v) {
            mListener.onClickView(getAdapterPosition());
        }

        public ExpandedViewHolder(View v, final RecyclerViewClickListener listener) {
            super(v);
            ButterKnife.bind(this, v);
            v.setOnClickListener(this);

            // this listener is our adapter
            mListener = listener;

            // set a listener for edit, delete calls
            mExpandedCostView.setIndividualCostViewClickListener(this);
        }
    }


    public CostItemAdapter(MainPresenter.View view, Context context) {
        mCostList = new ArrayList<>();
        mView = view;
        mContext = context;
        mSelectedItems = new HashSet<>();
    }

    @Override
    public int getItemViewType(int position) {
        // check to see if a view at this position should be expanded or normal/contracted
        if (mSelectedItems.contains(position))
            return ViewType.EXPANDED_CARD.ordinal();

        return ViewType.CONTRACTED_CARD.ordinal();
    }

    @Override
    public void onClickView(int position) {

        // If clicked on for the first time the view should be counted as selected, if clicked again the view
        // should be considered unselected.
        // Selected views will be shown as expanded cards while unselected will be shown as normal/contracted cards
        if (!mSelectedItems.contains(position))
            mSelectedItems.add(position);
        else
            mSelectedItems.remove(position);

        notifyItemChanged(position);
    }

    @Override
    public void onClickDelete(int position, long costId) {

        // in case we are deleting the last element from a day, mark that day as unselected, no point in showing an empty day
        if (mSelectedItems.contains(position) && mCostList.get(position).getCostList().size() == 1)
            mSelectedItems.remove(position);


        mView.onClickDeleteCost(costId);
    }

    @Override
    public void onClickEdit(int position, long costId) {
        mView.onClickEditCost(costId, position);
    }

    public void addNewCosts(@NonNull List<DailyTotalCost> costList) {
        // clean up old data
        if (mCostList != null) {
            mCostList.clear();
        }
        mCostList = costList;

        notifyDataSetChanged();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        // check if this should be an expanded card
        if (viewType == ViewType.EXPANDED_CARD.ordinal()) {
            View view = inflater.inflate(R.layout.card_expanded_daily_cost_item, parent, false);
            return new ExpandedViewHolder(view, this);
        }

        // this is a normal/contracted card
        View view = inflater.inflate(R.layout.card_daily_cost_item, parent, false);
        return new ViewHolder(view, this);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        DailyTotalCost cost = mCostList.get(position);

        // setup the views depending on the viewholder type
        if (viewHolder instanceof ViewHolder) {
            ((ViewHolder) viewHolder).setup(cost);
        } else if (viewHolder instanceof ExpandedViewHolder) {
            ((ExpandedViewHolder) viewHolder).mExpandedCostView.setDailyCost(cost);
        }
    }

    @Override
    public int getItemCount() {
        return mCostList.size();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/CostItemView.java
================================================
package com.kodelabs.mycosts.presentation.ui.customviews;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.ui.listeners.IndividualCostViewClickListener;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

/**
 * Created by dmilicic on 1/6/16.
 */
public class CostItemView extends RelativeLayout implements OnMenuItemClickListener {

    @Bind(R.id.cost_item_title)
    TextView mCategoryView;

    @Bind(R.id.cost_item_total_value)
    TextView mValueView;

    @Bind(R.id.cost_item_description)
    TextView mDescriptionView;

    @Bind(R.id.button_menu)
    ImageButton mMenuButton;

    private IndividualCostViewClickListener mCostViewClickListener;

    private Cost mCost;

    public CostItemView(Context context,
                        IndividualCostViewClickListener costViewClickListener, Cost cost) {
        super(context);
        mCostViewClickListener = costViewClickListener;
        mCost = cost;
        init(context);
    }

    private void init(Context context) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.individual_cost_item, this);

        ButterKnife.bind(this, view);

        setCategory(mCost.getCategory());
        setDescription(mCost.getDescription());
        setValue(mCost.getAmount());
    }


    @Override
    public boolean onMenuItemClick(MenuItem item) {

        // since the listener is set after this object is created it is possible that it can be null, avoid that :)
        if (mCostViewClickListener == null)
            return false;

        switch (item.getItemId()) {
            case R.id.item_edit:
                mCostViewClickListener.onClickEdit(mCost.getId());
                return true;
            case R.id.item_delete:
                mCostViewClickListener.onClickDelete(mCost.getId());
                return true;
            default:
                return false;
        }
    }


    @OnClick(R.id.button_menu)
    void onClickMenu() {
        PopupMenu popupMenu = new PopupMenu(getContext(), mMenuButton);
        popupMenu.setOnMenuItemClickListener(this);
        popupMenu.inflate(R.menu.menu_cost_item);
        popupMenu.show();
    }

    private void setCategory(String category) {
        mCategoryView.setText(category);
    }

    private void setValue(double value) {
        String val = String.format("%.2f$", value);
        mValueView.setText(val);
    }

    private void setDescription(String description) {
        mDescriptionView.setText(description);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/ExpandedCostView.java
================================================
package com.kodelabs.mycosts.presentation.ui.customviews;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.presentation.ui.listeners.IndividualCostViewClickListener;
import com.kodelabs.mycosts.utils.DateUtils;

import java.util.Date;
import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;

/**
 * Created by dmilicic on 1/6/16.
 */
public class ExpandedCostView extends CardView {

    @Bind(R.id.cost_item_title)
    TextView mTitle;

    @Bind(R.id.cost_item_total_value)
    TextView mValue;

    @Bind(R.id.layout_individual_cost_items)
    LinearLayout mLinearLayout;

    @Nullable
    private IndividualCostViewClickListener mIndividualCostViewClickListener;

    public ExpandedCostView(Context context) {
        super(context);
        init(context);
    }

    public ExpandedCostView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ExpandedCostView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.expanded_cost_item, this);

        ButterKnife.bind(this, view);
    }

    public void setIndividualCostViewClickListener(
            @Nullable IndividualCostViewClickListener individualCostViewClickListener) {
        mIndividualCostViewClickListener = individualCostViewClickListener;
    }

    private void addCostItem(Cost cost, int position) {
        CostItemView costView = new CostItemView(getContext(), mIndividualCostViewClickListener, cost);

        // every other cost item will have a different background so its easier on the eyes
        if (position % 2 == 0) {
            costView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.colorBrightGray));
        }

        mLinearLayout.addView(costView);
    }

    private void setTitle(Date date) {
        final String dateText = DateUtils.dateToText(getContext(), date);
        final String title = String.format(getContext().getString(R.string.total_expenses), dateText);
        mTitle.setText(title);
    }

    private void setTotalValue(double value) {
        String val = String.format("%.2f$", value);
        mValue.setText(val);
    }

    public void setDailyCost(DailyTotalCost dailyCost) {

        // reset the individual cost items
        mLinearLayout.removeAllViews();

        // convert date to text
        setTitle(dailyCost.getDate());

        setTotalValue(dailyCost.getTotalCost());

        // add the individual cost items
        List<Cost> costList = dailyCost.getCostList();
        Cost cost;
        for (int idx = 0; idx < costList.size(); idx++) {
            cost = costList.get(idx);
            addCostItem(cost, idx);
        }
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/fragments/DatePickerFragment.java
================================================
package com.kodelabs.mycosts.presentation.ui.fragments;

import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;

import java.util.Calendar;

/**
 * Created by dmilicic on 12/20/15.
 */
public class DatePickerFragment extends DialogFragment {

    public DatePickerFragment() {
        // empty
    }

    private DatePickerDialog.OnDateSetListener mListener;

    public void setListener(OnDateSetListener listener) {
        mListener = listener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the current date as the default date in the picker
        final Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);

        // Create a new instance of DatePickerDialog and return it
        return new DatePickerDialog(getActivity(), mListener, year, month, day);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/IndividualCostViewClickListener.java
================================================
package com.kodelabs.mycosts.presentation.ui.listeners;

/**
 * Created by dmilicic on 1/6/16.
 */
public interface IndividualCostViewClickListener {

    void onClickDelete(long costId);

    void onClickEdit(long costId);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/RecyclerViewClickListener.java
================================================
package com.kodelabs.mycosts.presentation.ui.listeners;

/**
 * Created by dmilicic on 12/26/15.
 */
public interface RecyclerViewClickListener {

    void onClickView(int position);

    void onClickEdit(int position, long costId);

    void onClickDelete(int position, long costId);
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/storage/CostRepositoryImpl.java
================================================
package com.kodelabs.mycosts.storage;

import android.content.Context;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.storage.converters.StorageModelConverter;
import com.kodelabs.mycosts.storage.model.Cost_Table;
import com.kodelabs.mycosts.sync.SyncAdapter;
import com.kodelabs.mycosts.utils.DateUtils;
import com.raizlabs.android.dbflow.sql.language.SQLite;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Created by dmilicic on 12/13/15.
 */
public class CostRepositoryImpl implements CostRepository {

    private Context mContext;

    // let's generate some dummy data
    static {

        List<com.kodelabs.mycosts.storage.model.Cost> costs = SQLite.select()
                .from(com.kodelabs.mycosts.storage.model.Cost.class)
                .queryList();

        // if the database is empty, let's add some dummies
        if (costs.size() == 0) {

            // get the today's date for some sample cost items
            Calendar calendar = Calendar.getInstance();
            Date today = calendar.getTime();
            today = DateUtils.truncateHours(today); // set hours, minutes and seconds to 0 for simplicity

            // get yesterday as well
            calendar.add(Calendar.DATE, -1);
            Date yesterday = calendar.getTime();
            yesterday = DateUtils.truncateHours(yesterday); // set hours, minutes and seconds to 0 for simplicity

            // Since each cost is uniquely identified by a timestamp, we should make sure that the sample costs are
            // not created in the same millisecond, we simply pause a bit after each cost creation.
            try {
                com.kodelabs.mycosts.storage.model.Cost cost = new com.kodelabs.mycosts.storage.model.Cost("Groceries", "Bought some X and some Y", today, 100.0);
                cost.insert();
                Thread.sleep(100);
                cost = new com.kodelabs.mycosts.storage.model.Cost("Bills", "Bill for electricity", today, 50.0);
                cost.insert();
                Thread.sleep(100);


                Thread.sleep(100);
                cost = new com.kodelabs.mycosts.storage.model.Cost("Transportation", "I took an Uber ride", yesterday, 10.0);
                cost.insert();
                Thread.sleep(100);
                cost = new com.kodelabs.mycosts.storage.model.Cost("Entertainment", "I went to see Star Wars!", yesterday, 50.0);
                cost.insert();


            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public CostRepositoryImpl(Context context) {
        mContext = context;
    }


    @Override
    public void insert(Cost item) {
        com.kodelabs.mycosts.storage.model.Cost dbItem = StorageModelConverter.convertToStorageModel(item);

        // mark as unsynced
        dbItem.synced = false;
        dbItem.insert();

        SyncAdapter.triggerSync(mContext);
    }

    @Override
    public void update(Cost cost) {
        com.kodelabs.mycosts.storage.model.Cost dbItem = StorageModelConverter.convertToStorageModel(cost);

        // mark as unsynced
        dbItem.synced = false;
        dbItem.update();

        SyncAdapter.triggerSync(mContext);
    }

    @Override
    public Cost getCostById(long id) {
        com.kodelabs.mycosts.storage.model.Cost cost = SQLite
                .select()
                .from(com.kodelabs.mycosts.storage.model.Cost.class)
                .where(Cost_Table.id.eq(id))
                .querySingle();

        return StorageModelConverter.convertToDomainModel(cost);
    }

    @Override
    public List<Cost> getAllCosts() {

        List<com.kodelabs.mycosts.storage.model.Cost> costs = SQLite
                .select()
                .from(com.kodelabs.mycosts.storage.model.Cost.class)
                .queryList();

        return StorageModelConverter.convertListToDomainModel(costs);
    }


    @Override
    public List<Cost> getAllUnsyncedCosts() {

        List<com.kodelabs.mycosts.storage.model.Cost> costs = SQLite
                .select()
                .from(com.kodelabs.mycosts.storage.model.Cost.class)
                .where(Cost_Table.synced.eq(false))
                .queryList();

        return StorageModelConverter.convertListToDomainModel(costs);
    }

    @Override
    public void markSynced(List<Cost> costs) {
        // we have to convert it to the database model before storing
        List<com.kodelabs.mycosts.storage.model.Cost> unsyncedCosts =
                StorageModelConverter.convertListToStorageModel(costs);

        for (com.kodelabs.mycosts.storage.model.Cost cost : unsyncedCosts) {
            cost.synced = true;
            cost.update();
        }
    }

    @Override
    public void delete(Cost cost) {
        com.kodelabs.mycosts.storage.model.Cost dbItem = StorageModelConverter.convertToStorageModel(cost);
        dbItem.delete();

        SyncAdapter.triggerSync(mContext);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/storage/contentprovider/StubProvider.java
================================================
package com.kodelabs.mycosts.storage.contentprovider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

/**
 * Created by dmilicic on 2/11/16.
 * Define an implementation of ContentProvider that stubs out
 * all methods
 */
public class StubProvider extends ContentProvider {
    /*
     * Always return true, indicating that the
     * provider loaded correctly.
     */
    @Override
    public boolean onCreate() {
        return true;
    }

    /*
     * Return no type for MIME type
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }

    /*
     * query() always returns no results
     *
     */
    @Override
    public Cursor query(
            Uri uri,
            String[] projection,
            String selection,
            String[] selectionArgs,
            String sortOrder) {
        return null;
    }

    /*
     * insert() always returns null (no URI)
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    /*
     * delete() always returns "no rows affected" (0)
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    /*
     * update() always returns "no rows affected" (0)
     */
    public int update(
            Uri uri,
            ContentValues values,
            String selection,
            String[] selectionArgs) {
        return 0;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/storage/converters/StorageModelConverter.java
================================================
package com.kodelabs.mycosts.storage.converters;

import com.kodelabs.mycosts.storage.model.Cost;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by dmilicic on 2/11/16.
 */
public class StorageModelConverter {

    public static Cost convertToStorageModel(com.kodelabs.mycosts.domain.model.Cost cost) {
        Cost result = new Cost();
        result.setDescription(cost.getDescription());
        result.setAmount(cost.getAmount());
        result.setCategory(cost.getCategory());
        result.setDate(cost.getDate());
        result.setId(cost.getId());

        return result;
    }

    public static com.kodelabs.mycosts.domain.model.Cost convertToDomainModel(Cost cost) {

        String desc = cost.getDescription();
        double amount = cost.getAmount();
        String category = cost.getCategory();
        Date date = cost.getDate();
        long id = cost.getId();

        com.kodelabs.mycosts.domain.model.Cost result = new com.kodelabs.mycosts.domain.model.Cost(
                category,
                desc,
                date,
                amount,
                id
        );

        return result;
    }


    public static List<com.kodelabs.mycosts.domain.model.Cost> convertListToDomainModel(List<Cost> costs) {
        List<com.kodelabs.mycosts.domain.model.Cost> convertedCosts = new ArrayList<>();

        for (Cost cost : costs) {
            convertedCosts.add(convertToDomainModel(cost));
        }

        // cleanup
        costs.clear();
        costs = null;

        return convertedCosts;
    }


    public static List<Cost> convertListToStorageModel(List<com.kodelabs.mycosts.domain.model.Cost> costs) {
        List<Cost> convertedCosts = new ArrayList<>();

        for (com.kodelabs.mycosts.domain.model.Cost cost : costs) {
            convertedCosts.add(convertToStorageModel(cost));
        }

        // cleanup
        costs.clear();
        costs = null;

        return convertedCosts;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/storage/database/CostDatabase.java
================================================
package com.kodelabs.mycosts.storage.database;

import com.raizlabs.android.dbflow.annotation.Database;

/**
 * Created by dmilicic on 2/11/16.
 */
@Database(name = CostDatabase.NAME, version = CostDatabase.VERSION)
public class CostDatabase {
    public static final String NAME = "Costs_db";

    public static final int VERSION = 1;
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/storage/model/Cost.java
================================================
package com.kodelabs.mycosts.storage.model;

import com.kodelabs.mycosts.storage.database.CostDatabase;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.structure.BaseModel;

import java.util.Date;

/**
 * Created by dmilicic on 2/11/16.
 */
@Table(database = CostDatabase.class)
public class Cost extends BaseModel {


    @PrimaryKey
    private long id; // our base model already has an id, let's use it as a primary key

    @Column
    private String category;

    @Column
    private String description;

    @Column
    private Date date;

    @Column
    private double amount;

    @Column
    public boolean synced;

    public Cost() {
    }

    /**
     * This constructor is only used to create some dummy objects when the app starts.
     */
    public Cost(String category, String description, Date date, double amount) {
        // cost will be "uniquely" identified by the current timestamp
        this.id = new Date().getTime();
        this.category = category;
        this.description = description;
        this.date = date;
        this.amount = amount;
        this.synced = false;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }


    @Override
    public String toString() {
        return "Cost{" +
                "id=" + id +
                ", category='" + category + '\'' +
                ", description='" + description + '\'' +
                ", date=" + date +
                ", amount=" + amount +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/sync/SyncAdapter.java
================================================
package com.kodelabs.mycosts.sync;

import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;

import com.kodelabs.mycosts.R;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.network.RestClient;
import com.kodelabs.mycosts.network.converters.RESTModelConverter;
import com.kodelabs.mycosts.network.model.Payload;
import com.kodelabs.mycosts.network.model.RESTCost;
import com.kodelabs.mycosts.network.services.SyncService;
import com.kodelabs.mycosts.storage.CostRepositoryImpl;
import com.kodelabs.mycosts.utils.AuthUtils;

import java.io.IOException;
import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;

/**
 *  * Handle the transfer of data between a server and an
 *  * app, using the Android sync adapter framework.
 *  
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {

    private Context mContext;

    private CostRepository mCostRepository;

    private List<Cost> mUnsyncedCosts;

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mContext = context;
        mCostRepository = new CostRepositoryImpl(mContext);
    }

    public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        mContext = context;
        mCostRepository = new CostRepositoryImpl(mContext);
    }

    /**
     * This method will start a sync adapter that will upload data to the server.
     */
    public static void triggerSync(Context context) {
        // TODO sync adapter is forced for debugging purposes, remove this in production
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);

        // request a sync using sync adapter
        Account account = AuthUtils.getAccount(context);
        ContentResolver.requestSync(account, context.getString(R.string.stub_content_authority), settingsBundle);
    }


    private Callback<Void> mResponseCallback = new Callback<Void>() {
        @Override
        public void onResponse(Call<Void> call, Response<Void> response) {
            Timber.i("UPLOAD SUCCESS: %d", response.code());

            if (response.isSuccess()) {
                mCostRepository.markSynced(mUnsyncedCosts);
            }
        }

        @Override
        public void onFailure(Call<Void> call, Throwable t) {
            Timber.e("UPLOAD FAIL");
            t.printStackTrace();

            // try to sync again
            SyncResult syncResult = new SyncResult();
        }
    };

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
                              SyncResult syncResult) {
        Timber.i("STARTING SYNC...");

        // initialize the services we will use
        SyncService syncService = RestClient.getService(SyncService.class);

        // TODO: get the real user's name
        Payload payload = new Payload("default");

        // get all unsynced data
        mUnsyncedCosts = mCostRepository.getAllUnsyncedCosts();
        for (Cost cost : mUnsyncedCosts) {

            // convert to models suitable for transferring over network
            RESTCost restCost = RESTModelConverter.convertToRestModel(cost);
            payload.addCost(restCost);
        }

        // run the upload
        try {
            Response<Void> response = syncService.uploadData(payload).execute();

            Timber.i("UPLOAD SUCCESS: %d", response.code());

            // everything went well, mark local cost items as synced
            if (response.isSuccess()) {
                mCostRepository.markSynced(mUnsyncedCosts);
            }

        } catch (IOException e) { // something went wrong
            Timber.e("UPLOAD FAIL");
            // make it a soft error so the framework does the exponential backoff
            syncResult.stats.numIoExceptions += 1;

            Timber.d("Restarting sync in %d seconds", syncResult.delayUntil);
        }
    }
}

================================================
FILE: app/src/main/java/com/kodelabs/mycosts/sync/SyncService.java
================================================
package com.kodelabs.mycosts.sync;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by dmilicic on 2/11/16.
 * This service is the component that connects our SyncAdapter to our application as it is in another process.
 */
public class SyncService extends Service {

    // Storage for an instance of the sync adapter
    private static       SyncAdapter sSyncAdapter     = null;
    // Object to use as a thread-safe lock
    private static final Object      sSyncAdapterLock = new Object();

    /*
     * Instantiate the sync adapter object.
     */
    @Override
    public void onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }

    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        /*
         * Get the object that allows external processes
         * to call onPerformSync(). The object is created
         * in the base class code when the SyncAdapter
         * constructors call super()
         */
        return sSyncAdapter.getSyncAdapterBinder();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/Authenticator.java
================================================
package com.kodelabs.mycosts.sync.auth;

import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.os.Bundle;

/**
 * Implement AbstractAccountAuthenticator and stub out all
 * of its methods
 * <p/>
 * We need at least a stub Android authentication so we can use the Android Sync adapter mechanism.
 */
public class Authenticator extends AbstractAccountAuthenticator {
    // Simple constructor
    public Authenticator(Context context) {
        super(context);
    }

    // Editing properties is not supported
    @Override
    public Bundle editProperties(
            AccountAuthenticatorResponse r, String s) {
        throw new UnsupportedOperationException();
    }

    // Don't add additional accounts
    @Override
    public Bundle addAccount(
            AccountAuthenticatorResponse r,
            String s,
            String s2,
            String[] strings,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }

    // Ignore attempts to confirm credentials
    @Override
    public Bundle confirmCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }

    // Getting an authentication token is not supported
    @Override
    public Bundle getAuthToken(
            AccountAuthenticatorResponse r,
            Account account,
            String s,
            Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }

    // Getting a label for the auth token is not supported
    @Override
    public String getAuthTokenLabel(String s) {
        throw new UnsupportedOperationException();
    }

    // Updating user credentials is not supported
    @Override
    public Bundle updateCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            String s, Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }

    // Checking features for the account is not supported
    @Override
    public Bundle hasFeatures(
            AccountAuthenticatorResponse r,
            Account account, String[] strings) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/AuthenticatorService.java
================================================
package com.kodelabs.mycosts.sync.auth;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

/**
 * A bound Service that instantiates the authenticator
 * when started.
 */
public class AuthenticatorService extends Service {
    /**
     * Instance field that stores the authenticator object
     */
    private Authenticator mAuthenticator;

    @Override
    public void onCreate() {
        // Create a new authenticator object
        mAuthenticator = new Authenticator(this);
    }

    /**
     * When the system binds to this Service to make the RPC call
     * return the authenticator's IBinder.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder();
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/DummyAccountProvider.java
================================================
package com.kodelabs.mycosts.sync.auth;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;

import com.kodelabs.mycosts.R;

/**
 * Created by dmilicic on 2/11/16.
 */
public class DummyAccountProvider {

    public static Account getDummyAccount(Context context) {
        final String ACCOUNT = "dummyaccount";
        final String ACCOUNT_TYPE = context.getString(R.string.account_type);
        final String AUTHORITY = context.getString(R.string.stub_content_authority);

        // Create the account type and default account
        return new Account(ACCOUNT, ACCOUNT_TYPE);
    }

    /**
     * Create a new dummy account for the sync adapter
     *
     * @param context The application context
     */
    public static boolean CreateSyncAccount(Context context) {

        Account newAccount = getDummyAccount(context);

        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        Context.ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        return accountManager.addAccountExplicitly(newAccount, null, null);
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/threading/MainThreadImpl.java
================================================
package com.kodelabs.mycosts.threading;

import android.os.Handler;
import android.os.Looper;

import com.kodelabs.mycosts.domain.executor.MainThread;

/**
 * This class makes sure that the runnable we provide will be run on the main UI thread.
 * <p/>
 * Created by dmilicic on 7/29/15.
 */
public class MainThreadImpl implements MainThread {

    private static MainThread sMainThread;

    private Handler mHandler;

    private MainThreadImpl() {
        mHandler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void post(Runnable runnable) {
        mHandler.post(runnable);
    }

    public static MainThread getInstance() {
        if (sMainThread == null) {
            sMainThread = new MainThreadImpl();
        }

        return sMainThread;
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/utils/AuthUtils.java
================================================
package com.kodelabs.mycosts.utils;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;

import com.kodelabs.mycosts.R;

import timber.log.Timber;

/**
 * Created by dmilicic on 8/11/15.
 * <p/>
 * This class will store useful utility methods for managing accounts on Android.
 */
public class AuthUtils {

    /**
     * Create a new dummy account needed by the sync adapter.
     */
    public static Account createDummyAccount(Context context) {

        String accountName = "DummyAccount";
        String accountType = context.getString(R.string.account_type);

        // Create the account type and default account
        Account newAccount = new Account(accountName, accountType);

        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        Context.ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        if (accountManager.addAccountExplicitly(newAccount, null, null)) {
            Timber.i("Account created!");
        } else {
            /*
             * The account exists or some other error occurred.
             */
            Timber.e("Account could not be created!");
            return null;
        }

        return newAccount;
    }


    /**
     * Retrieves an account from the Android system if it exists.
     *
     * @param context The context of the application.
     * @return Returns an existing account or throws an exception if no accounts exist.
     */
    public static Account getAccount(Context context) {

        if (context == null)
            throw new IllegalArgumentException("Context is null!");

        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        Context.ACCOUNT_SERVICE);

        String accountType = context.getString(R.string.account_type);
        Account[] accounts = accountManager.getAccountsByType(accountType);

        if (accounts.length == 0)
            throw new IllegalStateException("There are is no account at all!");

        // return the one and only account
        return accounts[0];
    }
}


================================================
FILE: app/src/main/java/com/kodelabs/mycosts/utils/DateUtils.java
================================================
package com.kodelabs.mycosts.utils;

import android.content.Context;

import com.kodelabs.mycosts.R;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * Created by dmilicic on 9/20/15.
 */
public class DateUtils {

    /**
     * Converts a date to the textual representation of dates used by people.
     *
     * @param date
     * @return If the date is of today, then this method will return 'Today's'. If its yesterday then 'Yesterday' is returned.
     * Otherwise it returns the date in the form of dd.mm
     */
    public static String dateToText(Context context, Date date) {
        String textDate;

        // clear hours, minutes and smaller time units from the date
        date = truncateHours(date);

        Calendar c = Calendar.getInstance();

        // set the calendar to start of today
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        // and get that as a Date
        Date today = c.getTime();

        // get yesterday
        c.add(Calendar.DATE, -1);
        Date yesterday = c.getTime();


        if (date.equals(today)) { // test if today
            textDate = context.getString(R.string.today_s);
        } else if (date.equals(yesterday)) {  // test if yesterday
            textDate = context.getString(R.string.yesterday_s);
        } else {
            textDate = formatDate(date, new SimpleDateFormat("dd.MM"));
        }

        return textDate;
    }

    public static Date createDate(int year, int monthOfYear, int dayOfMonth) {
        Calendar c = Calendar.getInstance();

        // set the calendar to start of today
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        // setup the date
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, monthOfYear);
        c.set(Calendar.DAY_OF_MONTH, dayOfMonth);

        // and get that as a Date
        Date resultDate = c.getTime();
        return resultDate;
    }

    public static String formatDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy", Locale.US);
        return sdf.format(date);
    }

    public static String formatDate(Date date, SimpleDateFormat sdf) {
        return sdf.format(date);
    }

    public static Date getToday() {

        Calendar c = Calendar.getInstance();

        // set the calendar to start of today
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        // and get that as a Date
        Date today = c.getTime();

        return today;
    }

    public static Date truncateHours(Date date) {
        Calendar c = Calendar.getInstance();

        // set the calendar to start of today
        c.setTime(date);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        // and get that as a Date
        return c.getTime();
    }
}


================================================
FILE: app/src/main/res/anim/hold.xml
================================================
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">

    <translate
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="0%"
        android:toYDelta="0%" />

</set>


================================================
FILE: app/src/main/res/drawable/rounded_corner.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/colorPrimary" />

    <corners
        android:bottomLeftRadius="0dp"
        android:bottomRightRadius="0dp"
        android:topLeftRadius="3dp"
        android:topRightRadius="3dp" />
    <padding
        android:bottom="0dip"
        android:left="0dip"
        android:right="0dip"
        android:top="0dip" />
</shape>


================================================
FILE: app/src/main/res/layout/activity_about.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.kodelabs.mycosts.presentation.ui.activities.AboutActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_about" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>


================================================
FILE: app/src/main/res/layout/activity_add_cost.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.kodelabs.mycosts.presentation.ui.activities.AddCostActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>


    <include
        layout="@layout/content_add_cost"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize" />

</FrameLayout>


================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<io.codetail.widget.RevealFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".presentation.ui.activities.MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/AppTheme.AppBarOverlay" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/expenses_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize">

    </android.support.v7.widget.RecyclerView>

    <!-- This layout will be revealed when FAB is clicked -->
    <include
        layout="@layout/content_add_cost"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize" />

</io.codetail.widget.RevealFrameLayout>


================================================
FILE: app/src/main/res/layout/card_daily_cost_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.CardView
    android:id="@+id/cost_item_card"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="124dp"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:layout_marginTop="20dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/cost_item_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:text="Today's total expenses"
            android:textSize="15sp" />

        <TextView
            android:id="@+id/cost_item_total_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginStart="16dp"
            android:text="127$"
            android:textSize="40sp" />


    </RelativeLayout>

</android.support.v7.widget.CardView>


================================================
FILE: app/src/main/res/layout/card_expanded_daily_cost_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<com.kodelabs.mycosts.presentation.ui.customviews.ExpandedCostView
    android:id="@+id/card_expanded_costview"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:layout_marginTop="20dp"
    app:cardPreventCornerOverlap="false" />

================================================
FILE: app/src/main/res/layout/content_about.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.kodelabs.mycosts.presentation.ui.activities.AboutActivity"
    tools:showIn="@layout/activity_about">

    <TextView
        android:id="@+id/made_by_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/made_by"
        android:textSize="20sp" />


    <TextView
        android:id="@+id/website_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/made_by_textview"
        android:layout_centerHorizontal="true"
        android:autoLink="web"
        android:text="@string/kodelabs_website"
        android:textSize="20sp"
        android:textStyle="bold" />

</RelativeLayout>


================================================
FILE: app/src/main/res/layout/content_add_cost.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<io.codetail.widget.RevealFrameLayout
    android:id="@+id/reveal_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff"
    android:visibility="visible">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_column="0"
                android:layout_gravity="center"
                android:layout_margin="16dp"
                android:src="@drawable/ic_date_range_black_24dp" />


            <TextView
                android:id="@+id/input_date"
                style="@android:style/Widget.DeviceDefault.Light.Spinner"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="Enter date"
                android:padding="20dp" />

        </LinearLayout>


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">


            <EditText
                android:id="@+id/input_amount"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="0.00"
                android:inputType="numberDecimal"
                android:padding="16dp" />

            <TextView
                android:id="@+id/textview_amount"
                android:layout_width="80dp"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:ems="10"
                android:hint="USD"
                android:padding="20dp" />


        </RelativeLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/textview_category"
                android:layout_width="110dp"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="Category:"
                android:padding="20dp" />


            <Spinner
                android:id="@+id/input_cost_category"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:entries="@array/category_array"
                android:padding="16dp" />

        </LinearLayout>


        <EditText
            android:id="@+id/input_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="Enter description"
            android:inputType="text"
            android:padding="16dp" />


    </LinearLayout>


</io.codetail.widget.RevealFrameLayout>

================================================
FILE: app/src/main/res/layout/expanded_cost_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="130dp"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:layout_marginTop="20dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="65dp"
            android:background="@drawable/rounded_corner">

            <TextView
                android:id="@+id/cost_item_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_margin="20dp"
                android:text="Today's total expenses"
                android:textColor="#fff"
                android:textSize="17sp" />

            <TextView
                android:id="@+id/cost_item_total_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginEnd="20dp"
                android:layout_marginRight="20dp"
                android:paddingTop="5dp"
                android:text="127$"
                android:textColor="#fff"
                android:textSize="17sp" />

        </RelativeLayout>


        <LinearLayout
            android:id="@+id/layout_individual_cost_items"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/cost_item_total_value"
            android:orientation="vertical">


        </LinearLayout>

    </LinearLayout>


</merge>

================================================
FILE: app/src/main/res/layout/individual_cost_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="16dp">

    <TextView
        android:id="@+id/cost_item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Groceries"
        android:textSize="15sp" />


    <TextView
        android:id="@+id/cost_item_description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/cost_item_title"
        android:layout_margin="16dp"
        android:text="This description describes a describable item"
        android:textSize="12sp" />

    <ImageButton
        android:id="@+id/button_menu"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:background="@null"
        android:src="@drawable/ic_vertical_dots" />

    <TextView
        android:id="@+id/cost_item_total_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/cost_item_title"
        android:layout_marginEnd="25dp"
        android:layout_marginRight="25dp"
        android:layout_toLeftOf="@id/button_menu"
        android:layout_toStartOf="@id/button_menu"
        android:text="127$"
        android:textSize="15sp" />


</RelativeLayout>

================================================
FILE: app/src/main/res/menu/menu_add_cost.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_save"
        android:orderInCategory="1"
        android:title="Save"
        app:showAsAction="always" />
</menu>

================================================
FILE: app/src/main/res/menu/menu_cost_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">


    <item
        android:id="@+id/item_edit"
        android:title="Edit"
        android:visible="true"
        app:showAsAction="ifRoom|withText" />

    <item
        android:id="@+id/item_delete"
        android:title="Delete"
        android:visible="true"
        app:showAsAction="ifRoom|withText" />


</menu>

================================================
FILE: app/src/main/res/menu/menu_main.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".presentation.ui.activities.MainActivity">

    <item
        android:id="@+id/action_add_cost"
        android:actionViewClass="android.widget.ImageButton"
        android:icon="@drawable/ic_add_white_24dp"
        android:orderInCategory="1"
        android:title="@string/action_add_cost"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/action_about"
        android:orderInCategory="100"
        android:title="@string/action_about"
        app:showAsAction="never" />
</menu>


================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBrightGray">#f1f1f1</color>
</resources>


================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="fab_margin">16dp</dimen>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">My Costs</string>

    <!-- Actions -->
    <string name="action_about">About</string>
    <string name="action_add_cost">Add new cost</string>

    <!-- General -->
    <string name="today_s">Today\'s</string>
    <string name="yesterday_s">Yesterday\'s</string>
    <string name="total_expenses">%1$s total costs</string>
    <string name="cost">%1$s cost</string>

    <!-- About -->
    <string name="made_by">Made by Dario Milicic</string>
    <string name="kodelabs_website">www.kodelabs.co</string>

    <!-- Titles -->
    <string name="title_activity_add_cost">Add cost</string>
    <string name="title_activity_about">About</string>

    <string name="account_type">com.kodelabs.mycosts.account</string>
    <string name="stub_content_authority">com.kodelabs.mycosts.provider</string>

    <string-array name="category_array">
        <item>Entertainment</item>
        <item>Equipment</item>
        <item>Gifts</item>
        <item>Groceries</item>
        <item>Insurance</item>
        <item>Medical</item>
        <item>Payment</item>
        <item>Rent</item>
        <item>Salary</item>
        <item>Shopping</item>
        <item>Tickets</item>
        <item>Transfer</item>
        <item>Transportation</item>
        <item>Utilities</item>
        <item>Bills</item>
        <item>Other</item>
    </string-array>

</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

</resources>


================================================
FILE: app/src/main/res/values-v21/styles.xml
================================================
<resources>>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>


================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
<resources>
    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
         (such as screen margins) for screens with more than 820dp of available width. This
         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
    <dimen name="activity_horizontal_margin">64dp</dimen>
</resources>


================================================
FILE: app/src/main/res/xml/authenticator.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@android:drawable/ic_dialog_info"
    android:label="@string/app_name"
    android:smallIcon="@android:drawable/ic_dialog_info" />

================================================
FILE: app/src/main/res/xml/syncadapter.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:allowParallelSyncs="false"
    android:contentAuthority="@string/stub_content_authority"
    android:isAlwaysSyncable="true"
    android:supportsUploading="true"
    android:userVisible="false" />

================================================
FILE: app/src/test/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdTest.java
================================================
package com.kodelabs.mycosts.domain.interactors;

import com.kodelabs.mycosts.domain.executor.Executor;
import com.kodelabs.mycosts.domain.executor.MainThread;
import com.kodelabs.mycosts.domain.interactors.impl.GetCostByIdInteractorImpl;
import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.domain.repository.CostRepository;
import com.kodelabs.mycosts.threading.TestMainThread;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Date;

import static org.mockito.Mockito.when;


/**
 * Created by dmilicic on 1/8/16.
 */
public class GetCostByIdTest {

    private       MainThread                     mMainThread;
    @Mock private Executor                       mExecutor;
    @Mock private CostRepository                 mCostRepository;
    @Mock private GetCostByIdInteractor.Callback mMockedCallback;

    private long mCostId;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mMainThread = new TestMainThread();
        mCostId = 100; // any number will do as cost ID
    }

    @Test
    public void testCostNotFound() throws Exception {
        GetCostByIdInteractorImpl interactor = new GetCostByIdInteractorImpl(mExecutor, mMainThread, mCostId, mCostRepository, mMockedCallback);
        interactor.run();

        Mockito.verify(mCostRepository).getCostById(mCostId);
        Mockito.verifyNoMoreInteractions(mCostRepository);
        Mockito.verify(mMockedCallback).noCostFound();
    }

    @Test
    public void testCostFound() throws Exception {

        Cost dummyCost = new Cost("Category", "description", new Date(), 100.0);
        when(mCostRepository.getCostById(mCostId))
                .thenReturn(dummyCost);

        GetCostByIdInteractorImpl interactor = new GetCostByIdInteractorImpl(mExecutor, mMainThread, mCostId, mCostRepository, mMockedCallback);
        interactor.run();

        Mockito.verify(mCostRepository).getCostById(mCostId);
        Mockito.verifyNoMoreInteractions(mCostRepository);
        Mockito.verify(mMockedCallback).onCostRetrieved(dummyCost);
    }
}


================================================
FILE: app/src/test/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverterTest.java
================================================
package com.kodelabs.mycosts.presentation.converter;

import com.kodelabs.mycosts.domain.model.Cost;
import com.kodelabs.mycosts.presentation.model.DailyTotalCost;
import com.kodelabs.mycosts.util.TestDateUtil;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static org.junit.Assert.assertEquals;

/**
 * Created by dmilicic on 1/5/16.
 */
public class DailyTotalCostConverterTest {

    private static List<Cost> mCosts;

    @Test
    public void testDailyCostConversion() throws Exception {

        // init test
        mCosts = new ArrayList<>();
        mCosts.add(new Cost("Transportation", "ZET", TestDateUtil.getDate(2016, Calendar.JANUARY, 4), 100.0));
        mCosts.add(new Cost("Groceries", "ZET", TestDateUtil.getDate(2016, Calendar.JANUARY, 4), 200.0));
        mCosts.add(new Cost("Entertainment", "ZET", TestDateUtil.getDate(2016, Calendar.JANUARY, 4), 300.0));
        mCosts.add(new Cost("Bills", "HEP struja", TestDateUtil.getDate(2016, Calendar.JANUARY, 4), 400.0));

        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 150.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 110.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 240.0));

        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 1), 130.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 1), 230.0));

        List<DailyTotalCost> dailyTotalCosts = DailyTotalCostConverter.convertCostsToDailyCosts(mCosts);

        // there should be 3 daily cost objects created for 3 different days
        assertEquals(3, dailyTotalCosts.size());

        // first day should have 4 cost items and a total sum of 1000
        assertEquals(4, dailyTotalCosts.get(0).getCostList().size());
        assertEquals(1000.0, dailyTotalCosts.get(0).getTotalCost(), 0.00001);

        // second day should have 3 cost items and a total sum of 500
        assertEquals(3, dailyTotalCosts.get(1).getCostList().size());
        assertEquals(500.0, dailyTotalCosts.get(1).getTotalCost(), 0.00001);

        // third day should have 2 cost items and a total sum of 360
        assertEquals(2, dailyTotalCosts.get(2).getCostList().size());
        assertEquals(360.0, dailyTotalCosts.get(2).getTotalCost(), 0.00001);
    }

    @Test
    public void testDailyCostConversion2() throws Exception {

        // init test
        mCosts = new ArrayList<>();
        mCosts.add(new Cost("Transportation", "ZET", TestDateUtil.getDate(2016, Calendar.JANUARY, 4), 100.0));

        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 150.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 110.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 2), 240.0));

        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 1), 130.0));
        mCosts.add(new Cost("Transportation", "Description", TestDateUtil.getDate(2016, Calendar.JANUARY, 1), 230.0));

        List<DailyTotalCost> dailyTotalCosts = DailyTotalCostConverter.convertCostsToDailyCosts(mCosts);

        // there should be 3 daily cost objects created for 3 different days
        assertEquals(3, dailyTotalCosts.size());

        assertEquals(1, dailyTotalCosts.get(0).getCostList().size());
        assertEquals(100.0, dailyTotalCosts.get(0).getTotalCost(), 0.00001);

        assertEquals(3, dailyTotalCosts.get(1).getCostList().size());
        assertEquals(500.0, dailyTotalCosts.get(1).getTotalCost(), 0.00001);

        // third day should have 2 cost items and a total sum of 360
        assertEquals(2, dailyTotalCosts.get(2).getCostList().size());
        assertEquals(360.0, dailyTotalCosts.get(2).getTotalCost(), 0.00001);
    }
}


================================================
FILE: app/src/test/java/com/kodelabs/mycosts/threading/TestMainThread.java
================================================
package com.kodelabs.mycosts.threading;

import com.kodelabs.mycosts.domain.executor.MainThread;

/**
 * Created by dmilicic on 1/8/16.
 */
public class TestMainThread implements MainThread {

    @Override
    public void post(Runnable runnable) {
        // tests can run on this thread, no need to invoke other threads
        runnable.run();
    }
}


================================================
FILE: app/src/test/java/com/kodelabs/mycosts/util/TestDateUtil.java
================================================
package com.kodelabs.mycosts.util;

import java.util.Calendar;
import java.util.Date;

/**
 * Created by dmilicic on 1/9/16.
 */
public class TestDateUtil {

    public static Date getDate(int year, int month, int day) {
        Calendar calendar = Calendar.getInstance();
        // set the calendar to start of today
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(year, month, day);

        return calendar.getTime();
    }
}


================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://jitpack.io"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Oct 21 11:34:03 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle
================================================
include ':app'
Download .txt
gitextract_jhfjo9q6/

├── .gitignore
├── .idea/
│   ├── .name
│   ├── codeStyleSettings.xml
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── QA/
│   ├── findbugs/
│   │   └── findbugs-filter.xml
│   └── quality.gradle
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── kodelabs/
│       │               └── mycosts/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── kodelabs/
│       │   │           └── mycosts/
│       │   │               ├── AndroidApplication.java
│       │   │               ├── domain/
│       │   │               │   ├── executor/
│       │   │               │   │   ├── Executor.java
│       │   │               │   │   ├── MainThread.java
│       │   │               │   │   └── impl/
│       │   │               │   │       └── ThreadExecutor.java
│       │   │               │   ├── interactors/
│       │   │               │   │   ├── AddCostInteractor.java
│       │   │               │   │   ├── DeleteCostInteractor.java
│       │   │               │   │   ├── EditCostInteractor.java
│       │   │               │   │   ├── GetAllCostsInteractor.java
│       │   │               │   │   ├── GetCostByIdInteractor.java
│       │   │               │   │   ├── base/
│       │   │               │   │   │   ├── AbstractInteractor.java
│       │   │               │   │   │   └── Interactor.java
│       │   │               │   │   └── impl/
│       │   │               │   │       ├── AddCostInteractorImpl.java
│       │   │               │   │       ├── DeleteCostInteractorImpl.java
│       │   │               │   │       ├── EditCostInteractorImpl.java
│       │   │               │   │       ├── GetAllCostsInteractorImpl.java
│       │   │               │   │       └── GetCostByIdInteractorImpl.java
│       │   │               │   ├── model/
│       │   │               │   │   └── Cost.java
│       │   │               │   └── repository/
│       │   │               │       └── CostRepository.java
│       │   │               ├── network/
│       │   │               │   ├── RestClient.java
│       │   │               │   ├── converters/
│       │   │               │   │   └── RESTModelConverter.java
│       │   │               │   ├── model/
│       │   │               │   │   ├── Payload.java
│       │   │               │   │   └── RESTCost.java
│       │   │               │   └── services/
│       │   │               │       └── SyncService.java
│       │   │               ├── presentation/
│       │   │               │   ├── animation/
│       │   │               │   │   └── AnimatorFactory.java
│       │   │               │   ├── converter/
│       │   │               │   │   └── DailyTotalCostConverter.java
│       │   │               │   ├── model/
│       │   │               │   │   └── DailyTotalCost.java
│       │   │               │   ├── presenters/
│       │   │               │   │   ├── AbstractPresenter.java
│       │   │               │   │   ├── AddCostPresenter.java
│       │   │               │   │   ├── BasePresenter.java
│       │   │               │   │   ├── EditCostPresenter.java
│       │   │               │   │   ├── MainPresenter.java
│       │   │               │   │   └── impl/
│       │   │               │   │       ├── AddCostPresenterImpl.java
│       │   │               │   │       ├── EditCostPresenterImpl.java
│       │   │               │   │       └── MainPresenterImpl.java
│       │   │               │   └── ui/
│       │   │               │       ├── BaseView.java
│       │   │               │       ├── activities/
│       │   │               │       │   ├── AboutActivity.java
│       │   │               │       │   ├── AbstractCostActivity.java
│       │   │               │       │   ├── AddCostActivity.java
│       │   │               │       │   ├── EditCostActivity.java
│       │   │               │       │   └── MainActivity.java
│       │   │               │       ├── adapters/
│       │   │               │       │   └── CostItemAdapter.java
│       │   │               │       ├── customviews/
│       │   │               │       │   ├── CostItemView.java
│       │   │               │       │   └── ExpandedCostView.java
│       │   │               │       ├── fragments/
│       │   │               │       │   └── DatePickerFragment.java
│       │   │               │       └── listeners/
│       │   │               │           ├── IndividualCostViewClickListener.java
│       │   │               │           └── RecyclerViewClickListener.java
│       │   │               ├── storage/
│       │   │               │   ├── CostRepositoryImpl.java
│       │   │               │   ├── contentprovider/
│       │   │               │   │   └── StubProvider.java
│       │   │               │   ├── converters/
│       │   │               │   │   └── StorageModelConverter.java
│       │   │               │   ├── database/
│       │   │               │   │   └── CostDatabase.java
│       │   │               │   └── model/
│       │   │               │       └── Cost.java
│       │   │               ├── sync/
│       │   │               │   ├── SyncAdapter.java
│       │   │               │   ├── SyncService.java
│       │   │               │   └── auth/
│       │   │               │       ├── Authenticator.java
│       │   │               │       ├── AuthenticatorService.java
│       │   │               │       └── DummyAccountProvider.java
│       │   │               ├── threading/
│       │   │               │   └── MainThreadImpl.java
│       │   │               └── utils/
│       │   │                   ├── AuthUtils.java
│       │   │                   └── DateUtils.java
│       │   └── res/
│       │       ├── anim/
│       │       │   └── hold.xml
│       │       ├── drawable/
│       │       │   └── rounded_corner.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_add_cost.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── card_daily_cost_item.xml
│       │       │   ├── card_expanded_daily_cost_item.xml
│       │       │   ├── content_about.xml
│       │       │   ├── content_add_cost.xml
│       │       │   ├── expanded_cost_item.xml
│       │       │   └── individual_cost_item.xml
│       │       ├── menu/
│       │       │   ├── menu_add_cost.xml
│       │       │   ├── menu_cost_item.xml
│       │       │   └── menu_main.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       ├── values-v21/
│       │       │   └── styles.xml
│       │       ├── values-w820dp/
│       │       │   └── dimens.xml
│       │       └── xml/
│       │           ├── authenticator.xml
│       │           └── syncadapter.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── kodelabs/
│                       └── mycosts/
│                           ├── domain/
│                           │   └── interactors/
│                           │       └── GetCostByIdTest.java
│                           ├── presentation/
│                           │   └── converter/
│                           │       └── DailyTotalCostConverterTest.java
│                           ├── threading/
│                           │   └── TestMainThread.java
│                           └── util/
│                               └── TestDateUtil.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (337 symbols across 64 files)

FILE: app/src/androidTest/java/com/kodelabs/mycosts/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: app/src/main/java/com/kodelabs/mycosts/AndroidApplication.java
  class AndroidApplication (line 14) | public class AndroidApplication extends Application {
    method onCreate (line 15) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/Executor.java
  type Executor (line 10) | public interface Executor {
    method execute (line 18) | void execute(final AbstractInteractor interactor);

FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/MainThread.java
  type MainThread (line 10) | public interface MainThread {
    method post (line 17) | void post(final Runnable runnable);

FILE: app/src/main/java/com/kodelabs/mycosts/domain/executor/impl/ThreadExecutor.java
  class ThreadExecutor (line 16) | public class ThreadExecutor implements Executor {
    method ThreadExecutor (line 29) | private ThreadExecutor() {
    method execute (line 39) | @Override
    method getInstance (line 57) | public static Executor getInstance() {

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/AddCostInteractor.java
  type AddCostInteractor (line 8) | public interface AddCostInteractor extends Interactor {
    type Callback (line 10) | interface Callback {
      method onCostAdded (line 11) | void onCostAdded();

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/DeleteCostInteractor.java
  type DeleteCostInteractor (line 9) | public interface DeleteCostInteractor extends Interactor {
    type Callback (line 11) | interface Callback {
      method onCostDeleted (line 12) | void onCostDeleted(Cost cost);

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/EditCostInteractor.java
  type EditCostInteractor (line 9) | public interface EditCostInteractor extends Interactor {
    type Callback (line 11) | interface Callback {
      method onCostUpdated (line 13) | void onCostUpdated(Cost cost);

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetAllCostsInteractor.java
  type GetAllCostsInteractor (line 13) | public interface GetAllCostsInteractor extends Interactor {
    type Callback (line 15) | interface Callback {
      method onCostsRetrieved (line 16) | void onCostsRetrieved(List<Cost> costList);

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdInteractor.java
  type GetCostByIdInteractor (line 9) | public interface GetCostByIdInteractor extends Interactor {
    type Callback (line 11) | interface Callback {
      method onCostRetrieved (line 12) | void onCostRetrieved(Cost cost);
      method noCostFound (line 14) | void noCostFound();

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/AbstractInteractor.java
  class AbstractInteractor (line 16) | public abstract class AbstractInteractor implements Interactor {
    method AbstractInteractor (line 24) | public AbstractInteractor(Executor threadExecutor, MainThread mainThre...
    method run (line 36) | public abstract void run();
    method cancel (line 38) | public void cancel() {
    method isRunning (line 43) | public boolean isRunning() {
    method onFinished (line 47) | public void onFinished() {
    method execute (line 52) | public void execute() {

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/Interactor.java
  type Interactor (line 6) | public interface Interactor {
    method execute (line 12) | void execute();

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/AddCostInteractorImpl.java
  class AddCostInteractorImpl (line 18) | public class AddCostInteractorImpl extends AbstractInteractor implements...
    method AddCostInteractorImpl (line 28) | public AddCostInteractorImpl(Executor threadExecutor, MainThread mainT...
    method run (line 40) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/DeleteCostInteractorImpl.java
  class DeleteCostInteractorImpl (line 15) | public class DeleteCostInteractorImpl extends AbstractInteractor impleme...
    method DeleteCostInteractorImpl (line 21) | public DeleteCostInteractorImpl(Executor threadExecutor,
    method run (line 30) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/EditCostInteractorImpl.java
  class EditCostInteractorImpl (line 17) | public class EditCostInteractorImpl extends AbstractInteractor implement...
    method EditCostInteractorImpl (line 31) | public EditCostInteractorImpl(Executor threadExecutor, MainThread main...
    method run (line 44) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetAllCostsInteractorImpl.java
  class GetAllCostsInteractorImpl (line 20) | public class GetAllCostsInteractorImpl extends AbstractInteractor implem...
    method compare (line 26) | @Override
    method GetAllCostsInteractorImpl (line 39) | public GetAllCostsInteractorImpl(Executor threadExecutor, MainThread m...
    method run (line 51) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetCostByIdInteractorImpl.java
  class GetCostByIdInteractorImpl (line 16) | public class GetCostByIdInteractorImpl extends AbstractInteractor implem...
    method GetCostByIdInteractorImpl (line 23) | public GetCostByIdInteractorImpl(Executor threadExecutor, MainThread m...
    method run (line 32) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/model/Cost.java
  class Cost (line 8) | public class Cost {
    method Cost (line 15) | public Cost(String category, String description, Date date, double amo...
    method Cost (line 31) | public Cost(String category, String description, Date date, double amo...
    method setCategory (line 39) | public void setCategory(String category) {
    method setDescription (line 43) | public void setDescription(String description) {
    method setDate (line 47) | public void setDate(Date date) {
    method setAmount (line 51) | public void setAmount(double amount) {
    method getId (line 55) | public long getId() {
    method getCategory (line 59) | public String getCategory() {
    method getDescription (line 63) | public String getDescription() {
    method getDate (line 67) | public Date getDate() {
    method getAmount (line 71) | public double getAmount() {
    method equals (line 75) | @Override
    method hashCode (line 86) | @Override
    method toString (line 91) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/domain/repository/CostRepository.java
  type CostRepository (line 10) | public interface CostRepository {
    method insert (line 12) | void insert(Cost cost);
    method update (line 14) | void update(Cost cost);
    method getCostById (line 16) | Cost getCostById(long id);
    method getAllCosts (line 18) | List<Cost> getAllCosts();
    method getAllUnsyncedCosts (line 20) | List<Cost> getAllUnsyncedCosts();
    method markSynced (line 22) | void markSynced(List<Cost> costs);
    method delete (line 24) | void delete(Cost cost);

FILE: app/src/main/java/com/kodelabs/mycosts/network/RestClient.java
  class RestClient (line 15) | public class RestClient {
    method getService (line 44) | public static <T> T getService(Class<T> serviceClass) {

FILE: app/src/main/java/com/kodelabs/mycosts/network/converters/RESTModelConverter.java
  class RESTModelConverter (line 11) | public class RESTModelConverter {
    method convertToRestModel (line 13) | public static RESTCost convertToRestModel(Cost cost) {

FILE: app/src/main/java/com/kodelabs/mycosts/network/model/Payload.java
  class Payload (line 13) | public class Payload {
    method Payload (line 21) | public Payload(String username) {
    method getUsername (line 26) | public String getUsername() {
    method getCosts (line 30) | public List<RESTCost> getCosts() {
    method addCost (line 34) | public void addCost(RESTCost cost) {
    method main (line 38) | public static void main(String[] args) {

FILE: app/src/main/java/com/kodelabs/mycosts/network/model/RESTCost.java
  class RESTCost (line 10) | public class RESTCost {
    method RESTCost (line 28) | public RESTCost(long id, String category, String description, Date dat...
    method getId (line 36) | public long getId() {
    method getCategory (line 40) | public String getCategory() {
    method getDescription (line 44) | public String getDescription() {
    method getDate (line 48) | public Date getDate() {
    method getAmount (line 52) | public double getAmount() {

FILE: app/src/main/java/com/kodelabs/mycosts/network/services/SyncService.java
  type SyncService (line 14) | public interface SyncService {
    method uploadData (line 19) | @Headers("Connection: close")

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/animation/AnimatorFactory.java
  class AnimatorFactory (line 20) | public class AnimatorFactory {
    method enterReveal (line 33) | public static void enterReveal(ViewGroup revealLayout, final Intent in...

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverter.java
  class DailyTotalCostConverter (line 13) | public class DailyTotalCostConverter {
    method convertCostsToDailyCosts (line 15) | public static List<DailyTotalCost> convertCostsToDailyCosts(List<Cost>...

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/model/DailyTotalCost.java
  class DailyTotalCost (line 11) | public class DailyTotalCost {
    method DailyTotalCost (line 18) | public DailyTotalCost(List<Cost> costList, Date date) {
    method getCostList (line 29) | public List<Cost> getCostList() {
    method getDate (line 33) | public Date getDate() {
    method getTotalCost (line 37) | public double getTotalCost() {
    method toString (line 41) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AbstractPresenter.java
  class AbstractPresenter (line 9) | public abstract class AbstractPresenter {
    method AbstractPresenter (line 13) | public AbstractPresenter(Executor executor, MainThread mainThread) {

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AddCostPresenter.java
  type AddCostPresenter (line 10) | public interface AddCostPresenter extends BasePresenter {
    type View (line 13) | interface View extends BaseView {
      method onCostAdded (line 15) | void onCostAdded();
    method addNewCost (line 18) | void addNewCost(Date date, double amount, String description, String c...

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/BasePresenter.java
  type BasePresenter (line 6) | public interface BasePresenter {
    method resume (line 11) | void resume();
    method pause (line 17) | void pause();
    method stop (line 23) | void stop();
    method destroy (line 29) | void destroy();
    method onError (line 35) | void onError(String message);

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/EditCostPresenter.java
  type EditCostPresenter (line 11) | public interface EditCostPresenter {
    type View (line 13) | interface View extends BaseView {
      method onCostRetrieved (line 15) | void onCostRetrieved(Cost cost);
      method onCostUpdated (line 17) | void onCostUpdated(Cost cost);
    method getCostById (line 20) | void getCostById(long id);
    method editCost (line 22) | void editCost(Cost cost, Date date, double amount, String description,...

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/MainPresenter.java
  type MainPresenter (line 12) | public interface MainPresenter extends BasePresenter {
    type View (line 14) | interface View extends BaseView {
      method showCosts (line 16) | void showCosts(List<DailyTotalCost> costs);
      method onClickDeleteCost (line 18) | void onClickDeleteCost(long costId);
      method onClickEditCost (line 20) | void onClickEditCost(long costId, int position);
      method onCostDeleted (line 22) | void onCostDeleted(Cost cost);
    method getAllCosts (line 25) | void getAllCosts();
    method deleteCost (line 27) | void deleteCost(long costId);

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/AddCostPresenterImpl.java
  class AddCostPresenterImpl (line 16) | public class AddCostPresenterImpl extends AbstractPresenter implements A...
    method AddCostPresenterImpl (line 22) | public AddCostPresenterImpl(Executor executor, MainThread mainThread,
    method addNewCost (line 29) | @Override
    method onCostAdded (line 42) | @Override
    method resume (line 48) | @Override
    method pause (line 53) | @Override
    method stop (line 58) | @Override
    method destroy (line 63) | @Override
    method onError (line 68) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/EditCostPresenterImpl.java
  class EditCostPresenterImpl (line 19) | public class EditCostPresenterImpl extends AbstractPresenter
    method EditCostPresenterImpl (line 25) | public EditCostPresenterImpl(Executor executor, MainThread mainThread,
    method getCostById (line 32) | @Override
    method onCostRetrieved (line 45) | @Override
    method noCostFound (line 50) | @Override
    method editCost (line 55) | @Override
    method onCostUpdated (line 68) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/MainPresenterImpl.java
  class MainPresenterImpl (line 21) | public class MainPresenterImpl extends AbstractPresenter implements Main...
    method MainPresenterImpl (line 28) | public MainPresenterImpl(Executor executor, MainThread mainThread,
    method resume (line 35) | @Override
    method pause (line 40) | @Override
    method stop (line 45) | @Override
    method destroy (line 50) | @Override
    method onError (line 55) | @Override
    method getAllCosts (line 60) | @Override
    method onCostsRetrieved (line 72) | @Override
    method deleteCost (line 78) | @Override
    method onCostDeleted (line 93) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/BaseView.java
  type BaseView (line 8) | public interface BaseView {
    method showProgress (line 14) | void showProgress();
    method hideProgress (line 19) | void hideProgress();
    method showError (line 26) | void showError(String message);

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AboutActivity.java
  class AboutActivity (line 13) | public class AboutActivity extends AppCompatActivity {
    method onCreate (line 15) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AbstractCostActivity.java
  class AbstractCostActivity (line 29) | public abstract class AbstractCostActivity extends AppCompatActivity
    method onCreate (line 56) | @Override
    method showDatePickerDialog (line 75) | @OnClick(R.id.input_date)
    method onCreateOptionsMenu (line 83) | @Override
    method extractFormData (line 90) | protected void extractFormData() {
    method onDateSet (line 103) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AddCostActivity.java
  class AddCostActivity (line 15) | public class AddCostActivity extends AbstractCostActivity
    method onCreate (line 20) | @Override
    method onResume (line 33) | @Override
    method onOptionsItemSelected (line 42) | @Override
    method onCostAdded (line 63) | @Override
    method showProgress (line 69) | @Override
    method hideProgress (line 74) | @Override
    method showError (line 79) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/EditCostActivity.java
  class EditCostActivity (line 21) | public class EditCostActivity extends AbstractCostActivity implements Ed...
    method onCreate (line 26) | @Override
    method onCostRetrieved (line 52) | @Override
    method findCategoryPosition (line 73) | private int findCategoryPosition(String category) {
    method prepopulateFields (line 83) | private void prepopulateFields() {
    method onOptionsItemSelected (line 95) | @Override
    method onCostUpdated (line 116) | @Override
    method showProgress (line 128) | @Override
    method hideProgress (line 133) | @Override
    method showError (line 138) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/MainActivity.java
  class MainActivity (line 35) | public class MainActivity extends AppCompatActivity implements MainPrese...
    method onCreate (line 51) | @Override
    method init (line 61) | private void init() {
    method onResume (line 87) | @Override
    method onCreateOptionsMenu (line 99) | @Override
    method onOptionsItemSelected (line 108) | @Override
    method onActivityResult (line 135) | @Override
    method showCosts (line 147) | @Override
    method onClickDeleteCost (line 153) | @Override
    method onCostDeleted (line 177) | @Override
    method onClickEditCost (line 183) | @Override
    method showProgress (line 193) | @Override
    method hideProgress (line 198) | @Override
    method showError (line 203) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/adapters/CostItemAdapter.java
  class CostItemAdapter (line 30) | public class CostItemAdapter extends RecyclerView.Adapter<RecyclerView.V...
    type ViewType (line 33) | private enum ViewType {
    class ViewHolder (line 44) | public static class ViewHolder extends RecyclerView.ViewHolder impleme...
      method setup (line 54) | public void setup(DailyTotalCost dailyTotalCost) {
      method onClick (line 63) | @Override
      method ViewHolder (line 68) | public ViewHolder(View v, final RecyclerViewClickListener listener) {
    class ExpandedViewHolder (line 76) | public static class ExpandedViewHolder extends RecyclerView.ViewHolder
      method onClickDelete (line 84) | @Override
      method onClickEdit (line 89) | @Override
      method onClick (line 94) | @Override
      method ExpandedViewHolder (line 99) | public ExpandedViewHolder(View v, final RecyclerViewClickListener li...
    method CostItemAdapter (line 113) | public CostItemAdapter(MainPresenter.View view, Context context) {
    method getItemViewType (line 120) | @Override
    method onClickView (line 129) | @Override
    method onClickDelete (line 143) | @Override
    method onClickEdit (line 154) | @Override
    method addNewCosts (line 159) | public void addNewCosts(@NonNull List<DailyTotalCost> costList) {
    method onCreateViewHolder (line 169) | @Override
    method onBindViewHolder (line 185) | @Override
    method getItemCount (line 197) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/CostItemView.java
  class CostItemView (line 24) | public class CostItemView extends RelativeLayout implements OnMenuItemCl...
    method CostItemView (line 42) | public CostItemView(Context context,
    method init (line 50) | private void init(Context context) {
    method onMenuItemClick (line 63) | @Override
    method onClickMenu (line 83) | @OnClick(R.id.button_menu)
    method setCategory (line 91) | private void setCategory(String category) {
    method setValue (line 95) | private void setValue(double value) {
    method setDescription (line 100) | private void setDescription(String description) {

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/ExpandedCostView.java
  class ExpandedCostView (line 28) | public class ExpandedCostView extends CardView {
    method ExpandedCostView (line 42) | public ExpandedCostView(Context context) {
    method ExpandedCostView (line 47) | public ExpandedCostView(Context context, AttributeSet attrs) {
    method ExpandedCostView (line 52) | public ExpandedCostView(Context context, AttributeSet attrs, int defSt...
    method init (line 57) | private void init(Context context) {
    method setIndividualCostViewClickListener (line 65) | public void setIndividualCostViewClickListener(
    method addCostItem (line 70) | private void addCostItem(Cost cost, int position) {
    method setTitle (line 81) | private void setTitle(Date date) {
    method setTotalValue (line 87) | private void setTotalValue(double value) {
    method setDailyCost (line 92) | public void setDailyCost(DailyTotalCost dailyCost) {

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/fragments/DatePickerFragment.java
  class DatePickerFragment (line 14) | public class DatePickerFragment extends DialogFragment {
    method DatePickerFragment (line 16) | public DatePickerFragment() {
    method setListener (line 22) | public void setListener(OnDateSetListener listener) {
    method onCreateDialog (line 26) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/IndividualCostViewClickListener.java
  type IndividualCostViewClickListener (line 6) | public interface IndividualCostViewClickListener {
    method onClickDelete (line 8) | void onClickDelete(long costId);
    method onClickEdit (line 10) | void onClickEdit(long costId);

FILE: app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/RecyclerViewClickListener.java
  type RecyclerViewClickListener (line 6) | public interface RecyclerViewClickListener {
    method onClickView (line 8) | void onClickView(int position);
    method onClickEdit (line 10) | void onClickEdit(int position, long costId);
    method onClickDelete (line 12) | void onClickDelete(int position, long costId);

FILE: app/src/main/java/com/kodelabs/mycosts/storage/CostRepositoryImpl.java
  class CostRepositoryImpl (line 20) | public class CostRepositoryImpl implements CostRepository {
    method CostRepositoryImpl (line 69) | public CostRepositoryImpl(Context context) {
    method insert (line 74) | @Override
    method update (line 85) | @Override
    method getCostById (line 96) | @Override
    method getAllCosts (line 107) | @Override
    method getAllUnsyncedCosts (line 119) | @Override
    method markSynced (line 131) | @Override
    method delete (line 143) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/storage/contentprovider/StubProvider.java
  class StubProvider (line 13) | public class StubProvider extends ContentProvider {
    method onCreate (line 18) | @Override
    method getType (line 26) | @Override
    method query (line 35) | @Override
    method insert (line 48) | @Override
    method delete (line 56) | @Override
    method update (line 64) | public int update(

FILE: app/src/main/java/com/kodelabs/mycosts/storage/converters/StorageModelConverter.java
  class StorageModelConverter (line 12) | public class StorageModelConverter {
    method convertToStorageModel (line 14) | public static Cost convertToStorageModel(com.kodelabs.mycosts.domain.m...
    method convertToDomainModel (line 25) | public static com.kodelabs.mycosts.domain.model.Cost convertToDomainMo...
    method convertListToDomainModel (line 45) | public static List<com.kodelabs.mycosts.domain.model.Cost> convertList...
    method convertListToStorageModel (line 60) | public static List<Cost> convertListToStorageModel(List<com.kodelabs.m...

FILE: app/src/main/java/com/kodelabs/mycosts/storage/database/CostDatabase.java
  class CostDatabase (line 8) | @Database(name = CostDatabase.NAME, version = CostDatabase.VERSION)

FILE: app/src/main/java/com/kodelabs/mycosts/storage/model/Cost.java
  class Cost (line 14) | @Table(database = CostDatabase.class)
    method Cost (line 36) | public Cost() {
    method Cost (line 42) | public Cost(String category, String description, Date date, double amo...
    method getId (line 52) | public long getId() {
    method setId (line 56) | public void setId(long id) {
    method getCategory (line 60) | public String getCategory() {
    method setCategory (line 64) | public void setCategory(String category) {
    method getDescription (line 68) | public String getDescription() {
    method setDescription (line 72) | public void setDescription(String description) {
    method getDate (line 76) | public Date getDate() {
    method setDate (line 80) | public void setDate(Date date) {
    method getAmount (line 84) | public double getAmount() {
    method setAmount (line 88) | public void setAmount(double amount) {
    method toString (line 93) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/sync/SyncAdapter.java
  class SyncAdapter (line 35) | public class SyncAdapter extends AbstractThreadedSyncAdapter {
    method SyncAdapter (line 43) | public SyncAdapter(Context context, boolean autoInitialize) {
    method SyncAdapter (line 49) | public SyncAdapter(Context context, boolean autoInitialize, boolean al...
    method triggerSync (line 58) | public static void triggerSync(Context context) {
    method onResponse (line 74) | @Override
    method onFailure (line 83) | @Override
    method onPerformSync (line 93) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/sync/SyncService.java
  class SyncService (line 12) | public class SyncService extends Service {
    method onCreate (line 22) | @Override
    method onBind (line 40) | @Nullable

FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/Authenticator.java
  class Authenticator (line 16) | public class Authenticator extends AbstractAccountAuthenticator {
    method Authenticator (line 18) | public Authenticator(Context context) {
    method editProperties (line 23) | @Override
    method addAccount (line 30) | @Override
    method confirmCredentials (line 41) | @Override
    method getAuthToken (line 50) | @Override
    method getAuthTokenLabel (line 60) | @Override
    method updateCredentials (line 66) | @Override
    method hasFeatures (line 75) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/AuthenticatorService.java
  class AuthenticatorService (line 11) | public class AuthenticatorService extends Service {
    method onCreate (line 17) | @Override
    method onBind (line 27) | @Override

FILE: app/src/main/java/com/kodelabs/mycosts/sync/auth/DummyAccountProvider.java
  class DummyAccountProvider (line 12) | public class DummyAccountProvider {
    method getDummyAccount (line 14) | public static Account getDummyAccount(Context context) {
    method CreateSyncAccount (line 28) | public static boolean CreateSyncAccount(Context context) {

FILE: app/src/main/java/com/kodelabs/mycosts/threading/MainThreadImpl.java
  class MainThreadImpl (line 13) | public class MainThreadImpl implements MainThread {
    method MainThreadImpl (line 19) | private MainThreadImpl() {
    method post (line 23) | @Override
    method getInstance (line 28) | public static MainThread getInstance() {

FILE: app/src/main/java/com/kodelabs/mycosts/utils/AuthUtils.java
  class AuthUtils (line 16) | public class AuthUtils {
    method createDummyAccount (line 21) | public static Account createDummyAccount(Context context) {
    method getAccount (line 57) | public static Account getAccount(Context context) {

FILE: app/src/main/java/com/kodelabs/mycosts/utils/DateUtils.java
  class DateUtils (line 15) | public class DateUtils {
    method dateToText (line 24) | public static String dateToText(Context context, Date date) {
    method createDate (line 57) | public static Date createDate(int year, int monthOfYear, int dayOfMont...
    method formatDate (line 76) | public static String formatDate(Date date) {
    method formatDate (line 81) | public static String formatDate(Date date, SimpleDateFormat sdf) {
    method getToday (line 85) | public static Date getToday() {
    method truncateHours (line 101) | public static Date truncateHours(Date date) {

FILE: app/src/test/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdTest.java
  class GetCostByIdTest (line 24) | public class GetCostByIdTest {
    method setUp (line 33) | @Before
    method testCostNotFound (line 40) | @Test
    method testCostFound (line 50) | @Test

FILE: app/src/test/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverterTest.java
  class DailyTotalCostConverterTest (line 18) | public class DailyTotalCostConverterTest {
    method testDailyCostConversion (line 22) | @Test
    method testDailyCostConversion2 (line 57) | @Test

FILE: app/src/test/java/com/kodelabs/mycosts/threading/TestMainThread.java
  class TestMainThread (line 8) | public class TestMainThread implements MainThread {
    method post (line 10) | @Override

FILE: app/src/test/java/com/kodelabs/mycosts/util/TestDateUtil.java
  class TestDateUtil (line 9) | public class TestDateUtil {
    method getDate (line 11) | public static Date getDate(int year, int month, int day) {
Condensed preview — 111 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (189K chars).
[
  {
    "path": ".gitignore",
    "chars": 405,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n=======\n# Built applica"
  },
  {
    "path": ".idea/.name",
    "chars": 8,
    "preview": "My Costs"
  },
  {
    "path": ".idea/codeStyleSettings.xml",
    "chars": 8900,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectCodeStyleSettingsManager\">\n    <o"
  },
  {
    "path": ".idea/compiler.xml",
    "chars": 709,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <option name"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 74,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"\" />\n</component>"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 684,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linke"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 3952,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EntryPointsManager\">\n    <entry_points v"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 351,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 2312,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Dario Miličić\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "QA/findbugs/findbugs-filter.xml",
    "chars": 615,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter>\n    <Match>\n        <!-- ignore all issues in resource generatio"
  },
  {
    "path": "QA/quality.gradle",
    "chars": 541,
    "preview": "apply plugin: 'findbugs'\n\ntask findbugs(type: FindBugs) {\n    ignoreFailures = true\n    effort = \"default\"\n    reportLev"
  },
  {
    "path": "README.md",
    "chars": 5423,
    "preview": "# Android Clean - Cost Tracker\nA sample cost-tracker app that showcases my Clean architecture approach to build Android "
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 1741,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'com.neenbedankt.android-apt'\napply from: \"${project.rootDir}/QA/q"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 675,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "app/src/androidTest/java/com/kodelabs/mycosts/ApplicationTest.java",
    "chars": 351,
    "preview": "package com.kodelabs.mycosts;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href="
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 3055,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"com.kodelabs.mycosts\"\n    xmlns:android=\"http://schemas.an"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/AndroidApplication.java",
    "chars": 589,
    "preview": "package com.kodelabs.mycosts;\n\nimport android.app.Application;\n\nimport com.facebook.stetho.Stetho;\nimport com.raizlabs.a"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/executor/Executor.java",
    "chars": 593,
    "preview": "package com.kodelabs.mycosts.domain.executor;\n\nimport com.kodelabs.mycosts.domain.interactors.base.AbstractInteractor;\n\n"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/executor/MainThread.java",
    "chars": 555,
    "preview": "package com.kodelabs.mycosts.domain.executor;\n\n/**\n * This interface will define a class that will enable interactors to"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/executor/impl/ThreadExecutor.java",
    "chars": 2094,
    "preview": "package com.kodelabs.mycosts.domain.executor.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com.kod"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/AddCostInteractor.java",
    "chars": 277,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.interactors.base.Interactor;\n\n/**\n "
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/DeleteCostInteractor.java",
    "chars": 338,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.interactors.base.Interactor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/EditCostInteractor.java",
    "chars": 337,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.interactors.base.Interactor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetAllCostsInteractor.java",
    "chars": 468,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.interactors.base.Interactor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdInteractor.java",
    "chars": 370,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.interactors.base.Interactor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/AbstractInteractor.java",
    "chars": 2123,
    "preview": "package com.kodelabs.mycosts.domain.interactors.base;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/base/Interactor.java",
    "chars": 313,
    "preview": "package com.kodelabs.mycosts.domain.interactors.base;\n\n/**\n * Created by dmilicic on 12/13/15.\n */\npublic interface Inte"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/AddCostInteractorImpl.java",
    "chars": 1946,
    "preview": "package com.kodelabs.mycosts.domain.interactors.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/DeleteCostInteractorImpl.java",
    "chars": 1618,
    "preview": "package com.kodelabs.mycosts.domain.interactors.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/EditCostInteractorImpl.java",
    "chars": 2581,
    "preview": "package com.kodelabs.mycosts.domain.interactors.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetAllCostsInteractorImpl.java",
    "chars": 2218,
    "preview": "package com.kodelabs.mycosts.domain.interactors.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/interactors/impl/GetCostByIdInteractorImpl.java",
    "chars": 1965,
    "preview": "package com.kodelabs.mycosts.domain.interactors.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/model/Cost.java",
    "chars": 2253,
    "preview": "package com.kodelabs.mycosts.domain.model;\n\nimport java.util.Date;\n\n/**\n * Created by dmilicic on 12/10/15.\n */\npublic c"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/domain/repository/CostRepository.java",
    "chars": 430,
    "preview": "package com.kodelabs.mycosts.domain.repository;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\n\nimport java.util.List;\n"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/network/RestClient.java",
    "chars": 1405,
    "preview": "package com.kodelabs.mycosts.network;\n\nimport com.facebook.stetho.okhttp3.StethoInterceptor;\n\nimport okhttp3.OkHttpClien"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/network/converters/RESTModelConverter.java",
    "chars": 585,
    "preview": "package com.kodelabs.mycosts.network.converters;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\nimport com.kodelabs.myc"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/network/model/Payload.java",
    "chars": 1272,
    "preview": "package com.kodelabs.mycosts.network.model;\n\nimport com.google.gson.Gson;\nimport com.google.gson.annotations.SerializedN"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/network/model/RESTCost.java",
    "chars": 1030,
    "preview": "package com.kodelabs.mycosts.network.model;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.Date;\n"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/network/services/SyncService.java",
    "chars": 526,
    "preview": "package com.kodelabs.mycosts.network.services;\n\nimport com.kodelabs.mycosts.network.model.Payload;\n\nimport retrofit2.Cal"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/animation/AnimatorFactory.java",
    "chars": 3426,
    "preview": "package com.kodelabs.mycosts.presentation.animation;\n\nimport android.animation.Animator;\nimport android.animation.Animat"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverter.java",
    "chars": 2220,
    "preview": "package com.kodelabs.mycosts.presentation.converter;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\nimport com.kodelabs"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/model/DailyTotalCost.java",
    "chars": 1059,
    "preview": "package com.kodelabs.mycosts.presentation.model;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\n\nimport java.util.Date;"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AbstractPresenter.java",
    "chars": 472,
    "preview": "package com.kodelabs.mycosts.presentation.presenters;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com."
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/AddCostPresenter.java",
    "chars": 399,
    "preview": "package com.kodelabs.mycosts.presentation.presenters;\n\nimport com.kodelabs.mycosts.presentation.ui.BaseView;\n\nimport jav"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/BasePresenter.java",
    "chars": 985,
    "preview": "package com.kodelabs.mycosts.presentation.presenters;\n\n/**\n * Created by dmilicic on 7/28/15.\n */\npublic interface BaseP"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/EditCostPresenter.java",
    "chars": 517,
    "preview": "package com.kodelabs.mycosts.presentation.presenters;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\nimport com.kodelab"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/MainPresenter.java",
    "chars": 647,
    "preview": "package com.kodelabs.mycosts.presentation.presenters;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\nimport com.kodelab"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/AddCostPresenterImpl.java",
    "chars": 1822,
    "preview": "package com.kodelabs.mycosts.presentation.presenters.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/EditCostPresenterImpl.java",
    "chars": 2361,
    "preview": "package com.kodelabs.mycosts.presentation.presenters.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/presenters/impl/MainPresenterImpl.java",
    "chars": 2737,
    "preview": "package com.kodelabs.mycosts.presentation.presenters.impl;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/BaseView.java",
    "chars": 803,
    "preview": "package com.kodelabs.mycosts.presentation.ui;\n\n/**\n * Created by dmilicic on 7/28/15.\n * <p/>\n * This interface represen"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AboutActivity.java",
    "chars": 1271,
    "preview": "package com.kodelabs.mycosts.presentation.ui.activities;\n\nimport android.content.Intent;\nimport android.net.Uri;\nimport "
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AbstractCostActivity.java",
    "chars": 3154,
    "preview": "package com.kodelabs.mycosts.presentation.ui.activities;\n\nimport android.app.DatePickerDialog;\nimport android.os.Bundle;"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/AddCostActivity.java",
    "chars": 2272,
    "preview": "package com.kodelabs.mycosts.presentation.ui.activities;\n\nimport android.os.Bundle;\nimport android.view.MenuItem;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/EditCostActivity.java",
    "chars": 4291,
    "preview": "package com.kodelabs.mycosts.presentation.ui.activities;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimpor"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/activities/MainActivity.java",
    "chars": 6351,
    "preview": "package com.kodelabs.mycosts.presentation.ui.activities;\n\nimport android.app.AlertDialog;\nimport android.content.DialogI"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/adapters/CostItemAdapter.java",
    "chars": 6585,
    "preview": "package com.kodelabs.mycosts.presentation.ui.adapters;\n\nimport android.content.Context;\nimport android.support.annotatio"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/CostItemView.java",
    "chars": 3013,
    "preview": "package com.kodelabs.mycosts.presentation.ui.customviews;\n\nimport android.content.Context;\nimport android.view.LayoutInf"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/customviews/ExpandedCostView.java",
    "chars": 3415,
    "preview": "package com.kodelabs.mycosts.presentation.ui.customviews;\n\nimport android.content.Context;\nimport android.support.annota"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/fragments/DatePickerFragment.java",
    "chars": 1062,
    "preview": "package com.kodelabs.mycosts.presentation.ui.fragments;\n\nimport android.app.DatePickerDialog;\nimport android.app.DatePic"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/IndividualCostViewClickListener.java",
    "chars": 226,
    "preview": "package com.kodelabs.mycosts.presentation.ui.listeners;\n\n/**\n * Created by dmilicic on 1/6/16.\n */\npublic interface Indi"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/presentation/ui/listeners/RecyclerViewClickListener.java",
    "chars": 287,
    "preview": "package com.kodelabs.mycosts.presentation.ui.listeners;\n\n/**\n * Created by dmilicic on 12/26/15.\n */\npublic interface Re"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/storage/CostRepositoryImpl.java",
    "chars": 5060,
    "preview": "package com.kodelabs.mycosts.storage;\n\nimport android.content.Context;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\ni"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/storage/contentprovider/StubProvider.java",
    "chars": 1525,
    "preview": "package com.kodelabs.mycosts.storage.contentprovider;\n\nimport android.content.ContentProvider;\nimport android.content.Co"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/storage/converters/StorageModelConverter.java",
    "chars": 2000,
    "preview": "package com.kodelabs.mycosts.storage.converters;\n\nimport com.kodelabs.mycosts.storage.model.Cost;\n\nimport java.util.Arra"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/storage/database/CostDatabase.java",
    "chars": 338,
    "preview": "package com.kodelabs.mycosts.storage.database;\n\nimport com.raizlabs.android.dbflow.annotation.Database;\n\n/**\n * Created "
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/storage/model/Cost.java",
    "chars": 2286,
    "preview": "package com.kodelabs.mycosts.storage.model;\n\nimport com.kodelabs.mycosts.storage.database.CostDatabase;\nimport com.raizl"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/sync/SyncAdapter.java",
    "chars": 4572,
    "preview": "package com.kodelabs.mycosts.sync;\n\nimport android.accounts.Account;\nimport android.content.AbstractThreadedSyncAdapter;"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/sync/SyncService.java",
    "chars": 1498,
    "preview": "package com.kodelabs.mycosts.sync;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/sync/auth/Authenticator.java",
    "chars": 2472,
    "preview": "package com.kodelabs.mycosts.sync.auth;\n\nimport android.accounts.AbstractAccountAuthenticator;\nimport android.accounts.A"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/sync/auth/AuthenticatorService.java",
    "chars": 757,
    "preview": "package com.kodelabs.mycosts.sync.auth;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IB"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/sync/auth/DummyAccountProvider.java",
    "chars": 1352,
    "preview": "package com.kodelabs.mycosts.sync.auth;\n\nimport android.accounts.Account;\nimport android.accounts.AccountManager;\nimport"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/threading/MainThreadImpl.java",
    "chars": 785,
    "preview": "package com.kodelabs.mycosts.threading;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.kodelabs.mycos"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/utils/AuthUtils.java",
    "chars": 2416,
    "preview": "package com.kodelabs.mycosts.utils;\n\nimport android.accounts.Account;\nimport android.accounts.AccountManager;\nimport and"
  },
  {
    "path": "app/src/main/java/com/kodelabs/mycosts/utils/DateUtils.java",
    "chars": 3198,
    "preview": "package com.kodelabs.mycosts.utils;\n\nimport android.content.Context;\n\nimport com.kodelabs.mycosts.R;\n\nimport java.text.S"
  },
  {
    "path": "app/src/main/res/anim/hold.xml",
    "chars": 254,
    "preview": "<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shareInterpolator=\"false\">\n\n    <translate\n "
  },
  {
    "path": "app/src/main/res/drawable/rounded_corner.xml",
    "chars": 464,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid and"
  },
  {
    "path": "app/src/main/res/layout/activity_about.xml",
    "chars": 1427,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout xmlns:android=\"http://schemas.an"
  },
  {
    "path": "app/src/main/res/layout/activity_add_cost.xml",
    "chars": 1184,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 1188,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<io.codetail.widget.RevealFrameLayout xmlns:android=\"http://schemas.android.com/a"
  },
  {
    "path": "app/src/main/res/layout/card_daily_cost_item.xml",
    "chars": 1335,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<android.support.v7.widget.CardView\n    android:id=\"@+id/cost_item_card\"\n    xml"
  },
  {
    "path": "app/src/main/res/layout/card_expanded_daily_cost_item.xml",
    "chars": 544,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.kodelabs.mycosts.presentation.ui.customviews.ExpandedCostView\n    android:id"
  },
  {
    "path": "app/src/main/res/layout/content_about.xml",
    "chars": 1409,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/content_add_cost.xml",
    "chars": 3304,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<io.codetail.widget.RevealFrameLayout\n    android:id=\"@+id/reveal_layout\"\n    xml"
  },
  {
    "path": "app/src/main/res/layout/expanded_cost_item.xml",
    "chars": 2038,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:lay"
  },
  {
    "path": "app/src/main/res/layout/individual_cost_item.xml",
    "chars": 1817,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/menu/menu_add_cost.xml",
    "chars": 318,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/menu/menu_cost_item.xml",
    "chars": 481,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/menu/menu_main.xml",
    "chars": 697,
    "preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\""
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 258,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 253,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 1388,
    "preview": "<resources>\n    <string name=\"app_name\">My Costs</string>\n\n    <!-- Actions -->\n    <string name=\"action_about\">About</s"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 641,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n        <!-"
  },
  {
    "path": "app/src/main/res/values-v21/styles.xml",
    "chars": 328,
    "preview": "<resources>>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item na"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 358,
    "preview": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as s"
  },
  {
    "path": "app/src/main/res/xml/authenticator.xml",
    "chars": 316,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<account-authenticator xmlns:android=\"http://schemas.android.com/apk/res/android\""
  },
  {
    "path": "app/src/main/res/xml/syncadapter.xml",
    "chars": 367,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sync-adapter xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/test/java/com/kodelabs/mycosts/domain/interactors/GetCostByIdTest.java",
    "chars": 2195,
    "preview": "package com.kodelabs.mycosts.domain.interactors;\n\nimport com.kodelabs.mycosts.domain.executor.Executor;\nimport com.kodel"
  },
  {
    "path": "app/src/test/java/com/kodelabs/mycosts/presentation/converter/DailyTotalCostConverterTest.java",
    "chars": 4134,
    "preview": "package com.kodelabs.mycosts.presentation.converter;\n\nimport com.kodelabs.mycosts.domain.model.Cost;\nimport com.kodelabs"
  },
  {
    "path": "app/src/test/java/com/kodelabs/mycosts/threading/TestMainThread.java",
    "chars": 354,
    "preview": "package com.kodelabs.mycosts.threading;\n\nimport com.kodelabs.mycosts.domain.executor.MainThread;\n\n/**\n * Created by dmil"
  },
  {
    "path": "app/src/test/java/com/kodelabs/mycosts/util/TestDateUtil.java",
    "chars": 581,
    "preview": "package com.kodelabs.mycosts.util;\n\nimport java.util.Calendar;\nimport java.util.Date;\n\n/**\n * Created by dmilicic on 1/9"
  },
  {
    "path": "build.gradle",
    "chars": 629,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Wed Oct 21 11:34:03 PDT 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 855,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "settings.gradle",
    "chars": 15,
    "preview": "include ':app'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the dmilicic/android-clean-sample-app GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 111 files (167.7 KB), approximately 43.0k tokens, and a symbol index with 337 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!