├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── logo.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── logo.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── logo.png │ │ │ ├── drawable-hdpi │ │ │ │ ├── cards.png │ │ │ │ ├── ic_user.png │ │ │ │ └── ic_exchange.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── cards.png │ │ │ │ ├── ic_user.png │ │ │ │ └── ic_exchange.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── logo.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── logo.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── cards.png │ │ │ │ ├── ic_user.png │ │ │ │ └── ic_exchange.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── cards.png │ │ │ │ ├── ic_user.png │ │ │ │ └── ic_exchange.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── cards.png │ │ │ │ ├── ic_user.png │ │ │ │ └── ic_exchange.png │ │ │ ├── values │ │ │ │ ├── integers.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── xml │ │ │ │ └── file_paths.xml │ │ │ ├── drawable │ │ │ │ ├── avatar_placeholder.xml │ │ │ │ ├── list_item_background.xml │ │ │ │ ├── ic_done.xml │ │ │ │ ├── ic_chevron_right.xml │ │ │ │ ├── ic_back_right.xml │ │ │ │ ├── ic_back_left.xml │ │ │ │ ├── ic_close.xml │ │ │ │ ├── ic_edit.xml │ │ │ │ ├── ic_search.xml │ │ │ │ ├── avatar_edit.xml │ │ │ │ └── ic_avatar.xml │ │ │ ├── transition │ │ │ │ ├── slide_end.xml │ │ │ │ └── slide_start.xml │ │ │ ├── layout │ │ │ │ ├── card_detail_dialog.xml │ │ │ │ ├── toolbar.xml │ │ │ │ ├── item_card_field.xml │ │ │ │ ├── card_list_loader.xml │ │ │ │ ├── activity_crop.xml │ │ │ │ ├── toolbar_search.xml │ │ │ │ ├── activity_user.xml │ │ │ │ ├── view_toolbar.xml │ │ │ │ ├── item_card.xml │ │ │ │ ├── activity_welcome.xml │ │ │ │ ├── activity_home.xml │ │ │ │ ├── view_card_info.xml │ │ │ │ └── activity_exchange.xml │ │ │ └── values-pt │ │ │ │ └── strings.xml │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Lato-Bold.ttf │ │ │ │ └── Lato-Regular.ttf │ │ ├── java │ │ │ └── io │ │ │ │ └── bloco │ │ │ │ └── cardcase │ │ │ │ ├── presentation │ │ │ │ ├── exchange │ │ │ │ │ ├── CardWrapper.java │ │ │ │ │ ├── ExchangeContract.java │ │ │ │ │ ├── NearbyManager.java │ │ │ │ │ ├── CardSerializer.java │ │ │ │ │ ├── ExchangePresenter.java │ │ │ │ │ └── ExchangeActivity.java │ │ │ │ ├── home │ │ │ │ │ ├── SimpleTextWatcher.java │ │ │ │ │ ├── HomeContract.java │ │ │ │ │ ├── CardDetailDialog.java │ │ │ │ │ ├── HomePresenter.java │ │ │ │ │ └── HomeActivity.java │ │ │ │ ├── common │ │ │ │ │ ├── ImageLoader.java │ │ │ │ │ ├── DateTimeFormat.java │ │ │ │ │ ├── ErrorDisplayer.java │ │ │ │ │ ├── CardViewHolder.java │ │ │ │ │ ├── CircleTransform.java │ │ │ │ │ ├── Toolbar.java │ │ │ │ │ ├── SearchToolbar.java │ │ │ │ │ ├── CardAdapter.java │ │ │ │ │ ├── Bootstrap.java │ │ │ │ │ ├── FileHelper.java │ │ │ │ │ └── CardInfoView.java │ │ │ │ ├── user │ │ │ │ │ ├── UserContract.java │ │ │ │ │ ├── UserPresenter.java │ │ │ │ │ ├── CropAvatarActivity.java │ │ │ │ │ ├── AvatarPicker.java │ │ │ │ │ └── UserActivity.java │ │ │ │ ├── BaseActivity.java │ │ │ │ └── welcome │ │ │ │ │ └── WelcomeActivity.java │ │ │ │ ├── common │ │ │ │ ├── di │ │ │ │ │ ├── PerActivity.java │ │ │ │ │ ├── ActivityComponent.java │ │ │ │ │ ├── ActivityModule.java │ │ │ │ │ ├── ApplicationModule.java │ │ │ │ │ └── ApplicationComponent.java │ │ │ │ ├── analytics │ │ │ │ │ ├── AnalyticsTracker.java │ │ │ │ │ ├── AnswersTracker.java │ │ │ │ │ ├── AnalyticsService.java │ │ │ │ │ └── GoogleAnalyticsTracker.java │ │ │ │ └── Preconditions.java │ │ │ │ ├── domain │ │ │ │ ├── GetUserCard.java │ │ │ │ ├── SaveUserCard.java │ │ │ │ ├── GetReceivedCards.java │ │ │ │ └── SaveReceivedCards.java │ │ │ │ ├── data │ │ │ │ ├── Database.java │ │ │ │ ├── DatabaseHelper.java │ │ │ │ └── models │ │ │ │ │ └── Card.java │ │ │ │ └── AndroidApplication.java │ │ └── AndroidManifest.xml │ ├── debug │ │ └── assets │ │ │ └── avatars │ │ │ ├── avatar1.jpg │ │ │ ├── avatar2.jpg │ │ │ ├── avatar3.jpg │ │ │ ├── avatar4.jpg │ │ │ ├── avatar5.jpg │ │ │ ├── avatar6.jpg │ │ │ ├── avatar7.jpg │ │ │ ├── avatar8.jpg │ │ │ ├── avatar9.jpg │ │ │ └── avatar10.jpg │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── bloco │ │ │ └── cardcase │ │ │ └── domain │ │ │ ├── SaveUserCardTest.java │ │ │ ├── GetUserCardTest.java │ │ │ ├── SaveReceivedCardsTest.java │ │ │ ├── GetReceivedCardsTest.java │ │ │ └── presenters │ │ │ ├── ExchangePresenterTest.java │ │ │ └── HomePresenterTest.java │ └── androidTest │ │ └── java │ │ └── io │ │ └── bloco │ │ └── cardcase │ │ ├── helpers │ │ ├── AssertCurrentActivity.java │ │ └── CardFactory.java │ │ ├── presentation │ │ ├── CardSerializerTest.java │ │ ├── UserActivityOnOnboardingTest.java │ │ ├── HomeActivityTest.java │ │ └── UserActivityOnEditTest.java │ │ └── data │ │ └── DatabaseTest.java ├── release │ └── output.json ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── .circleci └── config.yml ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/mipmap-hdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/mipmap-mdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/mipmap-xhdpi/logo.png -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar1.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar2.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar3.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar4.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar5.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar6.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar7.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar8.jpg -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar9.jpg -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-hdpi/cards.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-mdpi/cards.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/mipmap-xxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/mipmap-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/debug/assets/avatars/avatar10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/debug/assets/avatars/avatar10.jpg -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-hdpi/ic_user.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-mdpi/ic_user.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xhdpi/cards.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xhdpi/ic_user.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxhdpi/cards.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxxhdpi/cards.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxhdpi/ic_user.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxxhdpi/ic_user.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_exchange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-hdpi/ic_exchange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_exchange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-mdpi/ic_exchange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_exchange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xhdpi/ic_exchange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_exchange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxhdpi/ic_exchange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_exchange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blocoio/cardcase/HEAD/app/src/main/res/drawable-xxxhdpi/ic_exchange.png -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 300 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/exchange/CardWrapper.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.exchange; 2 | 3 | import io.bloco.cardcase.data.models.Card; 4 | 5 | class CardWrapper { 6 | Card card; 7 | String avatarData; 8 | } -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":25,"versionName":"1.1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 08 11:21:20 BST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/transition/slide_end.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/transition/slide_start.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/di/PerActivity.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.di; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Scope; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Scope @Retention(RUNTIME) public @interface PerActivity { 9 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_item_background.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_detail_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chevron_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_right.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/analytics/AnalyticsTracker.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.analytics; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.Nullable; 5 | import java.util.Map; 6 | 7 | @SuppressWarnings("WeakerAccess") 8 | public interface AnalyticsTracker { 9 | void init(Context context); 10 | 11 | void trackEvent(String event, @Nullable Map eventParams); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_card_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_list_loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/home/SimpleTextWatcher.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.home; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | public abstract class SimpleTextWatcher implements TextWatcher { 7 | 8 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { 9 | } 10 | 11 | @Override public void onTextChanged(CharSequence s, int start, int before, int count) { 12 | onTextChanged(s.toString( 13 | 14 | )); 15 | } 16 | 17 | @Override public void afterTextChanged(Editable s) { 18 | } 19 | 20 | protected abstract void onTextChanged(String newText); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/domain/GetUserCard.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | @Singleton public class GetUserCard { 9 | 10 | private final Database database; 11 | 12 | public interface Callback { 13 | void onGetUserCard(Card userCard); 14 | } 15 | 16 | @Inject public GetUserCard(Database database) { 17 | this.database = database; 18 | } 19 | 20 | public void get(Callback callback) { 21 | Card userCard = this.database.getUserCard(); 22 | callback.onGetUserCard(userCard); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/domain/SaveUserCard.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | @Singleton public class SaveUserCard { 9 | 10 | private final Database database; 11 | 12 | public interface Callback { 13 | void onSaveUserCard(Card savedCard); 14 | } 15 | 16 | @Inject public SaveUserCard(Database database) { 17 | this.database = database; 18 | } 19 | 20 | public void save(Card userCard, Callback callback) { 21 | userCard.setIsUser(true); 22 | database.saveCard(userCard); 23 | callback.onSaveUserCard(userCard); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/domain/GetReceivedCards.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import java.util.List; 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | @Singleton public class GetReceivedCards { 10 | 11 | private final Database database; 12 | 13 | public interface Callback { 14 | void onGetReceivedCards(List receivedCards); 15 | } 16 | 17 | @Inject public GetReceivedCards(Database database) { 18 | this.database = database; 19 | } 20 | 21 | public void get(Callback callback) { 22 | List receivedCards = database.getReceivedCards(); 23 | callback.onGetReceivedCards(receivedCards); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #607D8B 4 | #00BFA5 5 | 6 | @color/primary 7 | #FAFAFA 8 | #000000 9 | 10 | #333333 11 | #999999 12 | #999999 13 | #FFFFFF 14 | #80FFFFFF 15 | 16 | #D8D8D8 17 | 18 | #DDDDDD 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_avatar.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle 16 | .gradle 17 | gradlew.bat 18 | build 19 | reports 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Finder 37 | .DS_Store 38 | 39 | # IntelliJ IDEA 40 | .idea 41 | *.iml 42 | *.ipl 43 | *.iws 44 | classes/ 45 | idea-classes/ 46 | coverage-error.log 47 | 48 | # Android private files 49 | fabric.properties 50 | keystore.jks 51 | google-services.json 52 | google_analytics_tracker.xml -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/exchange/ExchangeContract.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.exchange; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import java.util.List; 6 | 7 | import io.bloco.cardcase.data.models.Card; 8 | 9 | public class ExchangeContract { 10 | public interface View { 11 | 12 | void setupCards(List receivedCards); 13 | 14 | void notifyCardAdded(); 15 | 16 | void showCards(); 17 | 18 | void showDone(); 19 | 20 | void showError(@StringRes int messageRes); 21 | 22 | void openInvite(); 23 | 24 | void close(); 25 | 26 | void closeWithConfirmation(); 27 | } 28 | 29 | public interface Presenter { 30 | void start(View view); 31 | 32 | void stop(); 33 | 34 | void clickedInvite(); 35 | 36 | void clickedClose(); 37 | 38 | void clickedDone(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/home/HomeContract.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.home; 2 | 3 | import java.util.List; 4 | 5 | import io.bloco.cardcase.data.models.Card; 6 | 7 | public class HomeContract { 8 | public interface View { 9 | void showEmpty(); 10 | 11 | void showCards(List cards); 12 | 13 | void showEmptySearchResult(); 14 | 15 | void hideEmptySearchResult(); 16 | 17 | void openOnboarding(); 18 | 19 | void openUser(); 20 | 21 | void openExchange(); 22 | 23 | void openSearch(); 24 | 25 | void closeSearch(); 26 | } 27 | 28 | public interface Presenter { 29 | void start(View view); 30 | 31 | void clickedSearch(); 32 | 33 | void clickedCloseSearch(); 34 | 35 | void searchEntered(String query); 36 | 37 | void clickedUser(); 38 | 39 | void clickedExchange(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/analytics/AnswersTracker.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.analytics; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.Nullable; 5 | import com.crashlytics.android.answers.Answers; 6 | import com.crashlytics.android.answers.CustomEvent; 7 | import java.util.Map; 8 | 9 | public class AnswersTracker implements AnalyticsTracker { 10 | 11 | @Override public void init(Context context) { 12 | } 13 | 14 | @Override public void trackEvent(String eventName, @Nullable Map eventParams) { 15 | CustomEvent event = new CustomEvent(eventName); 16 | 17 | if (eventParams != null) { 18 | for (Map.Entry param : eventParams.entrySet()) { 19 | event.putCustomAttribute(param.getKey(), param.getValue()); 20 | } 21 | } 22 | 23 | Answers.getInstance().logCustom(event); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/domain/SaveReceivedCards.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import java.util.List; 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | @Singleton public class SaveReceivedCards { 10 | 11 | private final Database database; 12 | 13 | public interface Callback { 14 | void onSavedReceivedCards(List savedCards); 15 | } 16 | 17 | @Inject public SaveReceivedCards(Database database) { 18 | this.database = database; 19 | } 20 | 21 | public void save(List receivedCards, Callback callback) { 22 | for (Card receivedCard : receivedCards) { 23 | receivedCard.setIsUser(false); 24 | receivedCard.setCreatedAt(null); 25 | } 26 | database.saveCards(receivedCards); 27 | callback.onSavedReceivedCards(receivedCards); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/io/bloco/cardcase/domain/SaveUserCardTest.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import static org.mockito.Matchers.eq; 11 | import static org.mockito.Mockito.verify; 12 | 13 | public class SaveUserCardTest { 14 | 15 | private SaveUserCard saveUserCard; 16 | 17 | @Mock private Database database; 18 | @Mock private SaveUserCard.Callback callback; 19 | 20 | @Before public void setUp() { 21 | MockitoAnnotations.initMocks(this); 22 | saveUserCard = new SaveUserCard(database); 23 | } 24 | 25 | @Test public void testGet() { 26 | Card card = new Card(); 27 | saveUserCard.save(card, callback); 28 | 29 | verify(database).saveCard(eq(card)); 30 | verify(callback).onSaveUserCard(eq(card)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/analytics/AnalyticsService.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.analytics; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import javax.inject.Inject; 9 | import javax.inject.Singleton; 10 | 11 | @Singleton 12 | public class AnalyticsService { 13 | 14 | private boolean active; 15 | private List trackers; 16 | 17 | @Inject 18 | public AnalyticsService() { 19 | active = false; 20 | } 21 | 22 | public void init(Context context, AnalyticsTracker... trackers) { 23 | active = true; 24 | this.trackers = Arrays.asList(trackers); 25 | for (AnalyticsTracker tracker : trackers) { 26 | tracker.init(context); 27 | } 28 | } 29 | 30 | public void trackEvent(String event) { 31 | if (active) { 32 | for (AnalyticsTracker tracker : trackers) { 33 | tracker.trackEvent(event, null); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/common/ImageLoader.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.common; 2 | 3 | import android.content.Context; 4 | import android.graphics.BitmapFactory; 5 | import android.widget.ImageView; 6 | import com.squareup.picasso.Picasso; 7 | import java.io.File; 8 | import javax.inject.Inject; 9 | import javax.inject.Singleton; 10 | import timber.log.Timber; 11 | 12 | @Singleton public class ImageLoader { 13 | 14 | private final Context context; 15 | 16 | @Inject public ImageLoader(Context context) { 17 | this.context = context; 18 | } 19 | 20 | public void loadAvatar(ImageView imageView, String avatarPath) { 21 | if (BitmapFactory.decodeFile(avatarPath) == null) { 22 | Timber.w("Invalid avatar file"); 23 | } 24 | Picasso.with(context) 25 | .load(new File(avatarPath)) 26 | .fit() 27 | .centerCrop() 28 | .transform(new CircleTransform()) 29 | .into(imageView); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/user/UserContract.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.user; 2 | 3 | import io.bloco.cardcase.data.models.Card; 4 | 5 | public class UserContract { 6 | public interface View { 7 | void showUser(Card userCard); 8 | 9 | void showBack(); 10 | 11 | void hideBack(); 12 | 13 | void showCancel(); 14 | 15 | void hideCancel(); 16 | 17 | void showEditButton(); 18 | 19 | void hideEditButton(); 20 | 21 | void showDoneButton(); 22 | 23 | void hideDoneButton(); 24 | 25 | void enableEditMode(); 26 | 27 | void disabledEditMode(); 28 | 29 | void openHome(); 30 | 31 | void close(); 32 | } 33 | 34 | public interface Presenter { 35 | void start(View view, boolean onboarding); 36 | 37 | void clickedBack(); 38 | 39 | void clickedCancel(); 40 | 41 | void clickedEdit(); 42 | 43 | void clickedDone(Card updatedCard); 44 | 45 | void onCardChanged(Card updatedCard); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/di/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.di; 2 | 3 | import android.app.Activity; 4 | 5 | import dagger.Component; 6 | import io.bloco.cardcase.presentation.exchange.ExchangeActivity; 7 | import io.bloco.cardcase.presentation.home.HomeActivity; 8 | import io.bloco.cardcase.presentation.user.CropAvatarActivity; 9 | import io.bloco.cardcase.presentation.user.UserActivity; 10 | import io.bloco.cardcase.presentation.welcome.WelcomeActivity; 11 | 12 | @PerActivity 13 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 14 | public interface ActivityComponent { 15 | 16 | void inject(CropAvatarActivity cropAvatarActivity); 17 | 18 | void inject(HomeActivity activity); 19 | 20 | void inject(UserActivity activity); 21 | 22 | void inject(ExchangeActivity activity); 23 | 24 | void inject(WelcomeActivity activity); 25 | 26 | //Exposed to sub-graphs. 27 | @SuppressWarnings("unused") 28 | Activity activity(); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/io/bloco/cardcase/domain/GetUserCardTest.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import static org.mockito.Matchers.eq; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.Mockito.when; 13 | 14 | public class GetUserCardTest { 15 | 16 | private GetUserCard getUserCard; 17 | 18 | @Mock private Database database; 19 | @Mock private GetUserCard.Callback callback; 20 | 21 | @Before public void setUp() { 22 | MockitoAnnotations.initMocks(this); 23 | getUserCard = new GetUserCard(database); 24 | } 25 | 26 | @Test public void testGet() { 27 | Card card = new Card(); 28 | when(database.getUserCard()).thenReturn(card); 29 | 30 | getUserCard.get(callback); 31 | 32 | verify(callback).onGetUserCard(eq(card)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/Preconditions.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | // Based on http://google-collections.googlecode.com/svn-history/r78/trunk/javadoc/com/google/common/base/Preconditions.html 6 | 7 | public class Preconditions { 8 | 9 | @SuppressWarnings("unused") 10 | public static void checkArgument(boolean expression, @Nullable Object errorMessage) { 11 | if (!expression) { 12 | throw new IllegalArgumentException(String.valueOf(errorMessage)); 13 | } 14 | } 15 | 16 | public static void checkState(boolean expression, @Nullable Object errorMessage) { 17 | if (!expression) { 18 | throw new IllegalStateException(String.valueOf(errorMessage)); 19 | } 20 | } 21 | 22 | @SuppressWarnings("UnusedReturnValue") 23 | public static T checkNotNull(T reference, @Nullable Object errorMessage) { 24 | if (reference == null) { 25 | throw new NullPointerException(String.valueOf(errorMessage)); 26 | } 27 | return reference; 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/test/java/io/bloco/cardcase/domain/SaveReceivedCardsTest.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | 12 | import static org.mockito.Matchers.eq; 13 | import static org.mockito.Mockito.verify; 14 | 15 | public class SaveReceivedCardsTest { 16 | 17 | private SaveReceivedCards saveReceivedCards; 18 | 19 | @Mock private Database database; 20 | @Mock private SaveReceivedCards.Callback callback; 21 | 22 | @Before public void setUp() { 23 | MockitoAnnotations.initMocks(this); 24 | saveReceivedCards = new SaveReceivedCards(database); 25 | } 26 | 27 | @Test public void testGet() { 28 | List cards = Arrays.asList(new Card(), new Card()); 29 | saveReceivedCards.save(cards, callback); 30 | 31 | verify(database).saveCards(eq(cards)); 32 | verify(callback).onSavedReceivedCards(eq(cards)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/test/java/io/bloco/cardcase/domain/GetReceivedCardsTest.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.domain; 2 | 3 | import io.bloco.cardcase.data.Database; 4 | import io.bloco.cardcase.data.models.Card; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | 12 | import static org.mockito.Matchers.eq; 13 | import static org.mockito.Mockito.verify; 14 | import static org.mockito.Mockito.when; 15 | 16 | public class GetReceivedCardsTest { 17 | 18 | private GetReceivedCards getReceivedCards; 19 | 20 | @Mock private Database database; 21 | @Mock private GetReceivedCards.Callback callback; 22 | 23 | @Before public void setUp() { 24 | MockitoAnnotations.initMocks(this); 25 | getReceivedCards = new GetReceivedCards(database); 26 | } 27 | 28 | @Test public void testGet() { 29 | List cards = Arrays.asList(new Card(), new Card()); 30 | when(database.getReceivedCards()).thenReturn(cards); 31 | 32 | getReceivedCards.get(callback); 33 | 34 | verify(callback).onGetReceivedCards(eq(cards)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_crop.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 23 | 24 | 25 | 26 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/bloco/cardcase/helpers/AssertCurrentActivity.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.helpers; 2 | 3 | import android.app.Activity; 4 | import androidx.test.espresso.core.internal.deps.guava.collect.Iterables; 5 | import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; 6 | import androidx.test.runner.lifecycle.Stage; 7 | import java.util.Collection; 8 | 9 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 10 | import static org.junit.Assert.assertEquals; 11 | 12 | public class AssertCurrentActivity { 13 | public static void assertCurrentActivity(Class activityClass) { 14 | assertEquals(activityClass.getName(), getCurrentActivity().getComponentName().getClassName()); 15 | } 16 | 17 | private static Activity getCurrentActivity() { 18 | getInstrumentation().waitForIdleSync(); 19 | final Activity[] activity = new Activity[1]; 20 | getInstrumentation().runOnMainSync(() -> { 21 | Collection activities = 22 | ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); 23 | activity[0] = Iterables.getOnlyElement(activities); 24 | }); 25 | return activity[0]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/di/ActivityModule.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.di; 2 | 3 | import android.app.Activity; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import io.bloco.cardcase.presentation.exchange.ExchangeContract; 8 | import io.bloco.cardcase.presentation.exchange.ExchangePresenter; 9 | import io.bloco.cardcase.presentation.home.HomeContract; 10 | import io.bloco.cardcase.presentation.home.HomePresenter; 11 | import io.bloco.cardcase.presentation.user.UserContract; 12 | import io.bloco.cardcase.presentation.user.UserPresenter; 13 | 14 | @Module 15 | public class ActivityModule { 16 | private final Activity activity; 17 | 18 | public ActivityModule(Activity activity) { 19 | this.activity = activity; 20 | } 21 | 22 | @Provides 23 | @PerActivity 24 | Activity activity() { 25 | return this.activity; 26 | } 27 | 28 | @Provides 29 | @PerActivity 30 | public HomeContract.Presenter provideHomePresenter(HomePresenter presenter) { 31 | return presenter; 32 | } 33 | 34 | @Provides 35 | @PerActivity 36 | public UserContract.Presenter provideUserPresenter(UserPresenter presenter) { 37 | return presenter; 38 | } 39 | 40 | @Provides 41 | @PerActivity 42 | public ExchangeContract.Presenter provideExchangePresenter(ExchangePresenter presenter) { 43 | return presenter; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/common/DateTimeFormat.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.common; 2 | 3 | import android.content.res.Resources; 4 | import android.text.format.DateUtils; 5 | import io.bloco.cardcase.R; 6 | import java.util.Date; 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | @Singleton 11 | class DateTimeFormat { 12 | 13 | private static final int SECOND_MILLIS = 1000; 14 | private static final int MINUTE_MILLIS = 60 * SECOND_MILLIS; 15 | private static final int HOUR_MILLIS = 60 * MINUTE_MILLIS; 16 | 17 | private final Resources mResources; 18 | 19 | @Inject public DateTimeFormat(Resources resources) { 20 | mResources = resources; 21 | } 22 | 23 | public String getRelativeTimeSpanString(Date timestamp) { 24 | long time = timestamp.getTime(); 25 | long now = System.currentTimeMillis(); 26 | long diff = now - time; 27 | 28 | if (diff < MINUTE_MILLIS) { 29 | return mResources.getString(R.string.time_just_now); 30 | } else if (diff < 50 * MINUTE_MILLIS) { 31 | int minutes = Math.round(diff / (float) MINUTE_MILLIS); 32 | return mResources.getString(R.string.time_minutes, minutes); 33 | } else if (diff < 24 * HOUR_MILLIS) { 34 | int hours = Math.round(diff / (float) HOUR_MILLIS); 35 | return mResources.getString(R.string.time_hours, hours); 36 | } else { 37 | return DateUtils.getRelativeTimeSpanString(time).toString(); 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/home/CardDetailDialog.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.home; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.view.Window; 6 | import android.view.WindowManager; 7 | 8 | import butterknife.BindView; 9 | import butterknife.ButterKnife; 10 | import io.bloco.cardcase.R; 11 | import io.bloco.cardcase.common.di.PerActivity; 12 | import io.bloco.cardcase.data.models.Card; 13 | import io.bloco.cardcase.presentation.common.CardInfoView; 14 | 15 | import javax.inject.Inject; 16 | 17 | @PerActivity 18 | public class CardDetailDialog { 19 | 20 | private final Dialog dialog; 21 | 22 | @BindView(R.id.card_dialog_info) 23 | CardInfoView cardInfoView; 24 | 25 | // TODO: Inject only the activity context? 26 | @Inject 27 | public CardDetailDialog(Activity activity) { 28 | this.dialog = new Dialog(activity); 29 | this.dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 30 | this.dialog.setContentView(R.layout.card_detail_dialog); 31 | ButterKnife.bind(this, dialog); 32 | } 33 | 34 | public void show(Card card) { 35 | fillCardInfoInDialog(card); 36 | dialog.show(); 37 | Window window = dialog.getWindow(); 38 | if (window != null) 39 | window.setLayout( 40 | WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); 41 | } 42 | 43 | private void fillCardInfoInDialog(Card card) { 44 | cardInfoView.setCard(card); 45 | cardInfoView.showTime(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 14 | 15 | 19 | 20 | 21 | 22 | 29 | 30 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | 32 | 33 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/common/ErrorDisplayer.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.common; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import androidx.annotation.StringRes; 7 | import com.google.android.material.snackbar.Snackbar; 8 | import androidx.core.content.ContextCompat; 9 | import android.view.View; 10 | import android.widget.TextView; 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | @SuppressWarnings("unused") 15 | @Singleton public class ErrorDisplayer { 16 | 17 | private final Context context; 18 | private final Resources resources; 19 | 20 | @Inject public ErrorDisplayer(Context context, Resources resources) { 21 | this.context = context; 22 | this.resources = resources; 23 | } 24 | 25 | public void show(Activity activity, @StringRes int errorRes) { 26 | show(activity.findViewById(android.R.id.content), resources.getString(errorRes)); 27 | } 28 | 29 | public void show(View view, @StringRes int errorRes) { 30 | show(view, resources.getString(errorRes)); 31 | } 32 | 33 | private void show(View view, String errorMessage) { 34 | Snackbar snackbar = Snackbar.make(view, errorMessage, Snackbar.LENGTH_LONG); 35 | setTextColor(snackbar); 36 | snackbar.show(); 37 | } 38 | 39 | private void setTextColor(Snackbar snackbar) { 40 | View view = snackbar.getView(); 41 | TextView textView = view.findViewById(com.google.android.material.R.id.snackbar_text); 42 | textView.setTextColor(ContextCompat.getColor(context, android.R.color.white)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/di/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.di; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import com.google.gson.Gson; 6 | import com.j256.ormlite.dao.RuntimeExceptionDao; 7 | import com.j256.ormlite.support.ConnectionSource; 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import io.bloco.cardcase.AndroidApplication; 11 | import io.bloco.cardcase.data.DatabaseHelper; 12 | import io.bloco.cardcase.data.models.Card; 13 | import java.sql.SQLException; 14 | import javax.inject.Singleton; 15 | 16 | @Module public class ApplicationModule { 17 | private final AndroidApplication application; 18 | 19 | public ApplicationModule(AndroidApplication application) { 20 | this.application = application; 21 | } 22 | 23 | @Provides @Singleton public Context provideApplicationContext() { 24 | return application; 25 | } 26 | 27 | @Provides @Singleton public Resources provideResources(Context context) { 28 | return context.getResources(); 29 | } 30 | 31 | @Provides @Singleton public Gson provideGson() { 32 | return new Gson(); 33 | } 34 | 35 | @Provides @Singleton public RuntimeExceptionDao provideCardDao() { 36 | DatabaseHelper databaseHelper = 37 | new DatabaseHelper(application.getApplicationContext(), application.getMode()); 38 | ConnectionSource connectionSource = databaseHelper.getConnectionSource(); 39 | try { 40 | return RuntimeExceptionDao.createDao(connectionSource, Card.class); 41 | } catch (SQLException exception) { 42 | throw new RuntimeException(exception); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4dp 3 | 8dp 4 | 16dp 5 | 32dp 6 | 48dp 7 | 8 | 14sp 9 | 16sp 10 | 18sp 11 | 22sp 12 | 13 | 4dp 14 | 15 | 40dp 16 | 56dp 17 | 40dp 18 | @dimen/margin_small 19 | 56dp 20 | -48dp 21 | 22 | 56dp 23 | @dimen/margin_default 24 | 25 | @dimen/margin_double 26 | 128dp 27 | @dimen/margin_default 28 | 24sp 29 | 48dp 30 | 40dp 31 | 32 | 64dp 33 | @dimen/text_large 34 | 1px 35 | 56dp 36 | 37 | 88dp 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/analytics/GoogleAnalyticsTracker.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.analytics; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.Nullable; 5 | import com.google.android.gms.analytics.GoogleAnalytics; 6 | import com.google.android.gms.analytics.HitBuilders; 7 | import com.google.android.gms.analytics.Tracker; 8 | import io.bloco.cardcase.R; 9 | import java.util.Map; 10 | 11 | public class GoogleAnalyticsTracker implements AnalyticsTracker { 12 | private static final String CATEGORY_KEY = "category"; 13 | private static final String LABEL_KEY = "label"; 14 | private static final String VALUE_KEY = "value"; 15 | 16 | private static final String DEFAULT_CATEGORY = "All"; 17 | 18 | private Tracker tracker; 19 | 20 | @Override public void init(Context context) { 21 | GoogleAnalytics analytics = GoogleAnalytics.getInstance(context); 22 | tracker = analytics.newTracker(R.xml.google_analytics_tracker); 23 | } 24 | 25 | @Override public void trackEvent(String eventName, @Nullable Map eventParams) { 26 | HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder(); 27 | eventBuilder.setAction(eventName); 28 | 29 | if (eventParams != null && eventParams.containsKey(CATEGORY_KEY)) { 30 | eventBuilder.setCategory(eventParams.get(CATEGORY_KEY)); 31 | } else { 32 | eventBuilder.setCategory(DEFAULT_CATEGORY); 33 | } 34 | 35 | if (eventParams != null && eventParams.containsKey(LABEL_KEY)) { 36 | eventBuilder.setLabel(eventParams.get(LABEL_KEY)); 37 | } 38 | 39 | if (eventParams != null && eventParams.containsKey(VALUE_KEY)) { 40 | eventBuilder.setLabel(eventParams.get(VALUE_KEY)); 41 | } 42 | 43 | tracker.send(eventBuilder.build()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.core.app.ActivityCompat; 9 | import androidx.core.app.ActivityOptionsCompat; 10 | 11 | import io.bloco.cardcase.AndroidApplication; 12 | import io.bloco.cardcase.R; 13 | import io.bloco.cardcase.common.di.ActivityModule; 14 | import io.bloco.cardcase.common.di.ApplicationComponent; 15 | import io.bloco.cardcase.presentation.common.Toolbar; 16 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; 17 | 18 | public abstract class BaseActivity extends AppCompatActivity { 19 | 20 | protected Toolbar toolbar; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | } 26 | 27 | @Override 28 | protected void attachBaseContext(Context newBase) { 29 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); 30 | } 31 | 32 | protected ApplicationComponent getApplicationComponent() { 33 | return ((AndroidApplication) getApplication()).getApplicationComponent(); 34 | } 35 | 36 | protected ActivityModule getActivityModule() { 37 | return new ActivityModule(this); 38 | } 39 | 40 | protected void startActivityWithAnimation(Intent intent) { 41 | @SuppressWarnings("unchecked") 42 | Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle(); 43 | ActivityCompat.startActivity(this, intent, options); 44 | } 45 | 46 | protected void bindToolbar() { 47 | toolbar = findViewById(R.id.toolbar); 48 | } 49 | 50 | protected void finishWithAnimation() { 51 | ActivityCompat.finishAfterTransition(this); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/welcome/WelcomeActivity.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.welcome; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import javax.inject.Inject; 8 | 9 | import butterknife.ButterKnife; 10 | import butterknife.OnClick; 11 | import io.bloco.cardcase.R; 12 | import io.bloco.cardcase.common.analytics.AnalyticsService; 13 | import io.bloco.cardcase.common.di.ActivityComponent; 14 | import io.bloco.cardcase.common.di.DaggerActivityComponent; 15 | import io.bloco.cardcase.presentation.BaseActivity; 16 | import io.bloco.cardcase.presentation.user.UserActivity; 17 | 18 | @SuppressWarnings("unused") 19 | public class WelcomeActivity extends BaseActivity { 20 | 21 | @Inject 22 | AnalyticsService analyticsService; 23 | 24 | public static class Factory { 25 | public static Intent getIntent(Context context) { 26 | return new Intent(context, WelcomeActivity.class); 27 | } 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_welcome); 34 | initializeInjectors(); 35 | ButterKnife.bind(this); 36 | analyticsService.trackEvent("Welcome Screen"); 37 | } 38 | 39 | @OnClick(R.id.welcome_start) 40 | void onClickStart() { 41 | Intent intent = UserActivity.Factory.getOnboardingIntent(this); 42 | startActivity(intent); 43 | finishWithAnimation(); 44 | } 45 | 46 | private void initializeInjectors() { 47 | ActivityComponent component = DaggerActivityComponent.builder() 48 | .applicationComponent(getApplicationComponent()) 49 | .activityModule(getActivityModule()) 50 | .build(); 51 | component.inject(this); 52 | 53 | ButterKnife.bind(this); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/exchange/NearbyManager.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.exchange; 2 | 3 | import android.app.Activity; 4 | 5 | import com.google.android.gms.common.api.ApiException; 6 | import com.google.android.gms.common.api.Status; 7 | import com.google.android.gms.nearby.Nearby; 8 | import com.google.android.gms.nearby.messages.Message; 9 | import com.google.android.gms.nearby.messages.MessageListener; 10 | 11 | import javax.inject.Inject; 12 | 13 | import io.bloco.cardcase.common.di.PerActivity; 14 | 15 | @PerActivity 16 | public class NearbyManager extends MessageListener { 17 | 18 | private Message message; 19 | private Listener listener; 20 | private final Activity activity; 21 | 22 | public interface Listener { 23 | 24 | void onMessageReceived(byte[] messageBytes); 25 | 26 | void onError(Status status); 27 | } 28 | 29 | @Inject 30 | public NearbyManager(Activity activity) { 31 | this.activity = activity; 32 | } 33 | 34 | @Override 35 | public void onFound(Message message) { 36 | listener.onMessageReceived(message.getContent()); 37 | } 38 | //onLost not needed in our case 39 | 40 | public void start(byte[] messageBytes, Listener listener) { 41 | this.message = new Message(messageBytes); 42 | this.listener = listener; 43 | Nearby.getMessagesClient(activity).publish(message) 44 | .addOnFailureListener(e -> { 45 | ApiException apiException = (ApiException) e; 46 | listener.onError(new Status(apiException.getStatusCode())); 47 | }); 48 | Nearby.getMessagesClient(activity).subscribe(this); 49 | } 50 | 51 | public void stop() { 52 | // Clean up when the user leaves the activity. 53 | Nearby.getMessagesClient(activity).unpublish(message); 54 | Nearby.getMessagesClient(activity).unsubscribe(this); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Butter Knife 2 | -keep class butterknife.** { *; } 3 | -dontwarn butterknife.internal.** 4 | -keep class **$$ViewBinder { *; } 5 | 6 | -keepclasseswithmembernames class * { 7 | @butterknife.* ; 8 | } 9 | 10 | -keepclasseswithmembernames class * { 11 | @butterknife.* ; 12 | } 13 | 14 | # OrmLite 15 | -keep class com.j256.** 16 | -keepclassmembers class com.j256.** { *; } 17 | -keep enum com.j256.** 18 | -keepclassmembers enum com.j256.** { *; } 19 | -keep interface com.j256.** 20 | -keepclassmembers interface com.j256.** { *; } 21 | -keepattributes Signature 22 | -keepattributes *Annotation* 23 | -keepclassmembers class * { public (android.content.Context); } 24 | -keep class io.bloco.cardcase.data.models.** 25 | -keepclassmembers class io.bloco.cardcase.data.models.** { *; } 26 | 27 | # Picasso 28 | -dontwarn com.squareup.okhttp.** 29 | 30 | # Fabric 31 | -keep class com.crashlytics.** { *; } 32 | -keep class com.crashlytics.android.** 33 | -keepattributes SourceFile,LineNumberTable 34 | 35 | ## Gson 36 | # Gson uses generic type information stored in a class file when working with fields. Proguard 37 | # removes such information by default, so configure it to keep all of it. 38 | -keepattributes Signature 39 | 40 | # Gson specific classes 41 | -keep class sun.misc.Unsafe { *; } 42 | #-keep class com.google.gson.stream.** { *; } 43 | 44 | # Application classes that will be serialized/deserialized over Gson 45 | -keep class io.bloco.cardcase.data.models.** { *; } 46 | 47 | # SnakeYAML 48 | -keep class org.yaml.snakeyaml.** { public protected private *; } 49 | -keep class org.yaml.snakeyaml.** { public protected private *; } 50 | -dontwarn org.yaml.snakeyaml.** 51 | 52 | # Joda Time 53 | -dontwarn org.joda.convert.** 54 | -dontwarn org.joda.time.** 55 | -keep class org.joda.time.** { *; } 56 | -keep interface org.joda.time.** { *; } 57 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/exchange/CardSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.exchange; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.File; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | import io.bloco.cardcase.data.models.Card; 11 | import io.bloco.cardcase.presentation.common.FileHelper; 12 | import timber.log.Timber; 13 | 14 | @Singleton 15 | public class CardSerializer { 16 | 17 | private final Gson gson; 18 | private final FileHelper fileHelper; 19 | 20 | @Inject 21 | public CardSerializer(Gson gson, FileHelper fileHelper) { 22 | this.gson = gson; 23 | this.fileHelper = fileHelper; 24 | } 25 | 26 | public byte[] serialize(Card card) { 27 | CardWrapper cardWrapper = newCardWrapper(card); 28 | return gson.toJson(cardWrapper).getBytes(); 29 | } 30 | 31 | public Card deserialize(byte[] data) { 32 | String cardSerialized = new String(data); 33 | Timber.i("Card received: %s", cardSerialized); 34 | CardWrapper cardWrapper = gson.fromJson(cardSerialized, CardWrapper.class); 35 | return unwrapCard(cardWrapper); 36 | } 37 | 38 | private CardWrapper newCardWrapper(Card card) { 39 | CardWrapper cardWrapper = new CardWrapper(); 40 | cardWrapper.card = card; 41 | if (card.getAvatarPath() != null) { 42 | cardWrapper.avatarData = fileHelper.getBytesFromFile(card.getAvatar()); 43 | } 44 | return cardWrapper; 45 | } 46 | 47 | private Card unwrapCard(CardWrapper cardWrapper) { 48 | Card card = cardWrapper.card; 49 | if (card == null) { 50 | return null; 51 | } 52 | 53 | if (card.getAvatarPath() != null) { 54 | File avatarFile = fileHelper.createFinalImageFile(); 55 | fileHelper.saveBytesToFile(cardWrapper.avatarData, avatarFile); 56 | card.setAvatar(avatarFile); 57 | } 58 | 59 | return card; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/common/CardViewHolder.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.common; 2 | 3 | import android.view.View; 4 | import android.widget.ImageView; 5 | import android.widget.TextView; 6 | 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import javax.inject.Inject; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import io.bloco.cardcase.AndroidApplication; 14 | import io.bloco.cardcase.R; 15 | import io.bloco.cardcase.data.models.Card; 16 | import io.bloco.cardcase.presentation.home.CardDetailDialog; 17 | 18 | @SuppressWarnings("unused") 19 | public class CardViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 20 | 21 | @Inject 22 | DateTimeFormat dateTimeFormat; 23 | @Inject 24 | ImageLoader imageLoader; 25 | 26 | @BindView(R.id.card_avatar) 27 | ImageView avatar; 28 | @BindView(R.id.card_name) 29 | TextView name; 30 | @BindView(R.id.card_time) 31 | TextView time; 32 | 33 | private final CardDetailDialog cardDetailDialog; 34 | private Card card; 35 | 36 | public CardViewHolder(View view, CardDetailDialog cardDetailDialog) { 37 | super(view); 38 | view.setOnClickListener(this); 39 | this.cardDetailDialog = cardDetailDialog; 40 | 41 | ((AndroidApplication) view.getContext().getApplicationContext()).getApplicationComponent() 42 | .inject(this); 43 | ButterKnife.bind(this, view); 44 | } 45 | 46 | public void bind(Card card) { 47 | this.card = card; 48 | name.setText(card.getName()); 49 | 50 | CharSequence timeStr = dateTimeFormat.getRelativeTimeSpanString(card.getUpdatedAt()); 51 | time.setText(timeStr); 52 | 53 | if (card.hasAvatar()) { 54 | imageLoader.loadAvatar(avatar, card.getAvatarPath()); 55 | } 56 | } 57 | 58 | @Override 59 | public void onClick(View view) { 60 | cardDetailDialog.show(card); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 30 | 31 | 41 | 42 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/data/Database.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.data; 2 | 3 | import com.j256.ormlite.dao.RuntimeExceptionDao; 4 | import com.j256.ormlite.stmt.Where; 5 | import com.j256.ormlite.table.TableUtils; 6 | import io.bloco.cardcase.data.models.Card; 7 | import java.sql.SQLException; 8 | import java.util.Calendar; 9 | import java.util.Date; 10 | import java.util.List; 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | @Singleton public class Database { 15 | 16 | private final RuntimeExceptionDao cardDao; 17 | 18 | @Inject public Database(RuntimeExceptionDao cardDao) { 19 | this.cardDao = cardDao; 20 | } 21 | 22 | public Card getUserCard() { 23 | try { 24 | return getCardQuery().eq("isUser", true).queryForFirst(); 25 | } catch (SQLException exception) { 26 | throw new RuntimeException(exception); 27 | } 28 | } 29 | 30 | public List getReceivedCards() { 31 | try { 32 | return getCardQuery().eq("isUser", false).query(); 33 | } catch (SQLException exception) { 34 | throw new RuntimeException(exception); 35 | } 36 | } 37 | 38 | public void saveCard(final Card card) { 39 | card.setUpdatedAt(now()); 40 | if (card.getCreatedAt() == null) { 41 | card.setCreatedAt(card.getUpdatedAt()); 42 | } 43 | cardDao.createOrUpdate(card); 44 | } 45 | 46 | public void saveCards(List cards) { 47 | for (Card card : cards) { 48 | saveCard(card); 49 | } 50 | } 51 | 52 | public void clear() { 53 | try { 54 | TableUtils.clearTable(cardDao.getConnectionSource(), Card.class); 55 | } catch (SQLException exception) { 56 | throw new RuntimeException(exception); 57 | } 58 | } 59 | 60 | private Where getCardQuery() { 61 | return cardDao.queryBuilder().orderBy("updatedAt", false).where(); 62 | } 63 | 64 | private Date now() { 65 | return Calendar.getInstance().getTime(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/common/di/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.common.di; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import com.google.gson.Gson; 6 | import com.j256.ormlite.dao.RuntimeExceptionDao; 7 | import dagger.Component; 8 | import io.bloco.cardcase.common.analytics.AnalyticsService; 9 | import io.bloco.cardcase.data.Database; 10 | import io.bloco.cardcase.data.models.Card; 11 | import io.bloco.cardcase.domain.GetReceivedCards; 12 | import io.bloco.cardcase.domain.GetUserCard; 13 | import io.bloco.cardcase.domain.SaveReceivedCards; 14 | import io.bloco.cardcase.domain.SaveUserCard; 15 | import io.bloco.cardcase.presentation.common.Bootstrap; 16 | import io.bloco.cardcase.presentation.common.CardInfoView; 17 | import io.bloco.cardcase.presentation.common.CardViewHolder; 18 | import io.bloco.cardcase.presentation.common.ErrorDisplayer; 19 | import io.bloco.cardcase.presentation.common.ImageLoader; 20 | import io.bloco.cardcase.presentation.exchange.CardSerializer; 21 | import io.bloco.cardcase.presentation.user.AvatarPicker; 22 | import javax.inject.Singleton; 23 | 24 | @SuppressWarnings("unused") 25 | @Singleton @Component(modules = ApplicationModule.class) public interface ApplicationComponent { 26 | 27 | void inject(CardViewHolder cardViewHolder); 28 | 29 | void inject(CardInfoView cardInfoView); 30 | 31 | //Exposed to sub-graphs. 32 | Context context(); 33 | 34 | Resources resources(); 35 | 36 | Gson gson(); 37 | 38 | Database database(); 39 | 40 | AvatarPicker avatarPicker(); 41 | 42 | GetUserCard getUserCard(); 43 | 44 | GetReceivedCards getReceivedCards(); 45 | 46 | SaveUserCard saveUserCard(); 47 | 48 | SaveReceivedCards saveReceivedCards(); 49 | 50 | ImageLoader imageLoader(); 51 | 52 | CardSerializer cardSerializer(); 53 | 54 | AnalyticsService analyticsService(); 55 | 56 | Bootstrap bootstrap(); 57 | 58 | ErrorDisplayer errorDisplayer(); 59 | 60 | RuntimeExceptionDao cardDao(); 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/io/bloco/cardcase/presentation/common/CircleTransform.java: -------------------------------------------------------------------------------- 1 | package io.bloco.cardcase.presentation.common; 2 | 3 | /* 4 | * Copyright 2014 Julian Shen 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.graphics.Bitmap; 20 | import android.graphics.BitmapShader; 21 | import android.graphics.Canvas; 22 | import android.graphics.Paint; 23 | import com.squareup.picasso.Transformation; 24 | 25 | /** 26 | * Created by julian on 13/6/21. 27 | */ 28 | class CircleTransform implements Transformation { 29 | @Override public Bitmap transform(Bitmap source) { 30 | int size = Math.min(source.getWidth(), source.getHeight()); 31 | 32 | int x = (source.getWidth() - size) / 2; 33 | int y = (source.getHeight() - size) / 2; 34 | 35 | Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); 36 | if (squaredBitmap != source) { 37 | source.recycle(); 38 | } 39 | 40 | Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); 41 | 42 | Canvas canvas = new Canvas(bitmap); 43 | Paint paint = new Paint(); 44 | BitmapShader shader = 45 | new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); 46 | paint.setShader(shader); 47 | paint.setAntiAlias(true); 48 | 49 | float r = size / 2f; 50 | canvas.drawCircle(r, r, r, paint); 51 | 52 | squaredBitmap.recycle(); 53 | return bitmap; 54 | } 55 | 56 | @Override public String key() { 57 | return "circle"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | commands: 4 | cardcase_setup: 5 | description: "Get the app ready to test" 6 | steps: 7 | - checkout 8 | - run: 9 | name: Setup secret files 10 | command: | 11 | echo ${LOCAL_PROPERTIES} > ~/code/local.properties 12 | echo ${FABRIC_PROPERTIES} > ~/code/app/fabric.properties 13 | echo ${GOOGLE_ANALYTICS_TRACKER_XML} > ~/code/app/src/main/res/xml/google_analytics_tracker.xml 14 | echo ${GOOGLE_SERVICES_JSON} > ~/code/app/google-services.json 15 | - restore_cache: 16 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 17 | - run: 18 | name: Download Dependencies 19 | command: ./gradlew androidDependencies 20 | - save_cache: 21 | paths: 22 | - ~/.gradle 23 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 24 | 25 | executors: 26 | cardcase_executor: 27 | working_directory: ~/code 28 | docker: 29 | - image: circleci/android:api-28 30 | environment: 31 | JVM_OPTS: -Xmx3200m 32 | 33 | jobs: 34 | build: 35 | executor: cardcase_executor 36 | steps: 37 | - cardcase_setup 38 | - run: 39 | name: Build 40 | command: ./gradlew assemble 41 | lint: 42 | executor: cardcase_executor 43 | steps: 44 | - cardcase_setup 45 | - run: 46 | name: Run Lint 47 | command: ./gradlew lint 48 | - store_artifacts: 49 | path: app/build/reports 50 | destination: reports 51 | test: 52 | executor: cardcase_executor 53 | steps: 54 | - cardcase_setup 55 | - run: 56 | name: Run Tests 57 | command: ./gradlew test 58 | - store_test_results: 59 | path: app/build/test-results 60 | - store_artifacts: 61 | path: app/build/reports 62 | destination: reports 63 | 64 | workflows: 65 | version: 2.1 66 | build_lint_test: 67 | jobs: 68 | - build 69 | - lint: 70 | requires: 71 | - build 72 | - test: 73 | requires: 74 | - build -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_welcome.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 23 | 24 | 30 | 31 | 36 | 37 | 38 | 39 |