diff --git a/.gitignore b/.gitignore index 862ce6c..2b75303 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,13 @@ -*~ -*.DS_Store -*.class -*java# -*.settings -local.properties -.arc/ -bin/ -gen/ -out/ -target/ -.gradle/ -build/ -.idea/ -.gradle -gradle/ *.iml -remind101_test.jks -*.db -com_crashlytics_export_strings.xml -crashlytics-build.properties -.gradletasknamecache -app/gradle.properties - -nohup.out -*~ - -.tx/remind-android.stringsxml -app/gradle.properties \ No newline at end of file +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..2996d53 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..703e5d4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index eff22f1..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -Sample code illustrating principles described at [http://engineering.remind.com/android-code-that-scales/](http://engineering.remind.com/android-code-that-scales/) diff --git a/app/build.gradle b/app/build.gradle index e2e8ee8..7d79e83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,29 +1,42 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" - + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + compileSdkVersion 28 + buildToolsVersion "29.0.1" defaultConfig { applicationId "com.remind101.archexample" minSdkVersion 21 - targetSdkVersion 23 + targetSdkVersion 28 versionCode 1 versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + multiDexEnabled = true } buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.9.5' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' + api 'com.google.guava:guava:28.1-android' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + + implementation 'com.arello-mobile:moxy:1.5.5' + annotationProcessor 'com.arello-mobile:moxy-compiler:1.5.5' + implementation 'com.arello-mobile:moxy-android:1.5.5' + implementation 'com.arello-mobile:moxy-app-compat:1.5.5' - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.android.support:recyclerview-v7:23.1.1' - compile 'com.google.guava:guava:18.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 373c48f..f1b4245 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,17 +1,21 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/nbarraille/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# 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 *; #} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/remind101/archexample/ApplicationTest.java b/app/src/androidTest/java/com/remind101/archexample/ApplicationTest.java deleted file mode 100644 index 4374b1c..0000000 --- a/app/src/androidTest/java/com/remind101/archexample/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.remind101.archexample; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/com/remind101/archexample/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/remind101/archexample/ExampleInstrumentedTest.java new file mode 100644 index 0000000..68772f5 --- /dev/null +++ b/app/src/androidTest/java/com/remind101/archexample/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.remind101.archexample; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.remind101.archexample", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58fb446..513442e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,20 +1,20 @@ + package="com.remind101.archexample"> - + android:theme="@style/AppTheme"> + - + - - + \ No newline at end of file diff --git a/app/src/main/java/com/remind101/archexample/CounterAdapter.java b/app/src/main/java/com/remind101/archexample/CounterAdapter.java deleted file mode 100644 index f101e52..0000000 --- a/app/src/main/java/com/remind101/archexample/CounterAdapter.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.remind101.archexample; - -import android.support.annotation.NonNull; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import com.remind101.archexample.models.Counter; -import com.remind101.archexample.presenters.CounterPresenter; -import com.remind101.archexample.views.CounterViewHolder; - -public class CounterAdapter extends MvpRecyclerListAdapter { - @Override - public CounterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new CounterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.counter_row, parent, false)); - } - - @NonNull - @Override - protected CounterPresenter createPresenter(@NonNull Counter counter) { - CounterPresenter presenter = new CounterPresenter(); - presenter.setModel(counter); - return presenter; - } - - @NonNull - @Override - protected Object getModelId(@NonNull Counter model) { - return model.getId(); - } -} diff --git a/app/src/main/java/com/remind101/archexample/CounterDatabase.java b/app/src/main/java/com/remind101/archexample/CounterDatabase.java index cb54c4a..b7db4cc 100644 --- a/app/src/main/java/com/remind101/archexample/CounterDatabase.java +++ b/app/src/main/java/com/remind101/archexample/CounterDatabase.java @@ -15,8 +15,6 @@ public class CounterDatabase { private final Map counters; - private int nextId = 1; - private CounterDatabase() { counters = new HashMap<>(); } @@ -42,9 +40,7 @@ public List getAllCounters() { public void saveCounter(@NonNull Counter counter) { synchronized (counters) { - int id = nextId++; - counter.setId(id); - counters.put(id, counter); + counters.put(counter.getId(), counter); } } } diff --git a/app/src/main/java/com/remind101/archexample/MainActivity.java b/app/src/main/java/com/remind101/archexample/MainActivity.java index c3db100..16127bb 100644 --- a/app/src/main/java/com/remind101/archexample/MainActivity.java +++ b/app/src/main/java/com/remind101/archexample/MainActivity.java @@ -1,7 +1,6 @@ package com.remind101.archexample; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.Menu; @@ -9,39 +8,42 @@ import android.view.MenuItem; import android.widget.ViewAnimator; +import com.arellomobile.mvp.MvpAppCompatActivity; +import com.arellomobile.mvp.presenter.InjectPresenter; import com.remind101.archexample.models.Counter; -import com.remind101.archexample.presenters.MainPresenter; -import com.remind101.archexample.views.MainView; +import com.remind101.archexample.moxy.presenter.MainPresenter; +import com.remind101.archexample.presenters.IMainView; +import com.remind101.archexample.recyclerview_with_moxy.CounterAdapter; import java.util.List; -public class MainActivity extends AppCompatActivity implements MainView { +public class MainActivity extends MvpAppCompatActivity implements IMainView { + + // Нумерация слоев внутри ViewAnimator (FrameLayout) private static final int POSITION_LIST = 0; private static final int POSITION_LOADING = 1; private static final int POSITION_EMPTY = 2; private ViewAnimator animator; private CounterAdapter adapter; + private boolean menuStatus = false; - private MainPresenter presenter; + @InjectPresenter + public MainPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - presenter = new MainPresenter(); - } else { - presenter = PresenterManager.getInstance().restorePresenter(savedInstanceState); - } - setContentView(R.layout.activity_list); - animator = (ViewAnimator) findViewById(R.id.animator); + + animator = findViewById(R.id.animator); RecyclerView recyclerView = (RecyclerView) animator.getChildAt(POSITION_LIST); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); - adapter = new CounterAdapter(); + adapter = new CounterAdapter(getMvpDelegate()); + recyclerView.setAdapter(adapter); } @@ -49,6 +51,11 @@ protected void onCreate(Bundle savedInstanceState) { public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); + + for (int i = 0; i < menu.size(); i++) { + menu.getItem(i).setVisible(menuStatus); + } + return true; } @@ -63,25 +70,10 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - @Override - protected void onResume() { - super.onResume(); - - presenter.bindView(this); - } - - @Override - protected void onPause() { - super.onPause(); - - presenter.unbindView(); - } - @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - PresenterManager.getInstance().savePresenter(presenter, outState); } @Override @@ -99,4 +91,10 @@ public void showLoading() { public void showEmpty() { animator.setDisplayedChild(POSITION_EMPTY); } + + @Override + public void showMenu(boolean state) { + menuStatus = state; + invalidateOptionsMenu(); + } } diff --git a/app/src/main/java/com/remind101/archexample/MvpRecyclerAdapter.java b/app/src/main/java/com/remind101/archexample/MvpRecyclerAdapter.java deleted file mode 100644 index 72e8fe8..0000000 --- a/app/src/main/java/com/remind101/archexample/MvpRecyclerAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.remind101.archexample; - -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; - -import com.remind101.archexample.presenters.BasePresenter; - -import java.util.HashMap; -import java.util.Map; - -public abstract class MvpRecyclerAdapter extends RecyclerView.Adapter { - protected final Map presenters; - - public MvpRecyclerAdapter() { - presenters = new HashMap<>(); - } - - @NonNull protected P getPresenter(@NonNull M model) { - System.err.println("Getting presenter for item " + getModelId(model)); - return presenters.get(getModelId(model)); - } - - @NonNull protected abstract P createPresenter(@NonNull M model); - - @NonNull protected abstract Object getModelId(@NonNull M model); - - - @Override - public void onViewRecycled(VH holder) { - super.onViewRecycled(holder); - - holder.unbindPresenter(); - } - - @Override - public boolean onFailedToRecycleView(VH holder) { - // Sometimes, if animations are running on the itemView's children, the RecyclerView won't - // be able to recycle the view. We should still unbind the presenter. - holder.unbindPresenter(); - - return super.onFailedToRecycleView(holder); - } - - @Override - public void onBindViewHolder(VH holder, int position) { - holder.bindPresenter(getPresenter(getItem(position))); - } - - protected abstract M getItem(int position); -} diff --git a/app/src/main/java/com/remind101/archexample/MvpRecyclerListAdapter.java b/app/src/main/java/com/remind101/archexample/MvpRecyclerListAdapter.java deleted file mode 100644 index 095c1d4..0000000 --- a/app/src/main/java/com/remind101/archexample/MvpRecyclerListAdapter.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.remind101.archexample; - -import com.remind101.archexample.presenters.BasePresenter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public abstract class MvpRecyclerListAdapter> extends MvpRecyclerAdapter { - private final List models; - - public MvpRecyclerListAdapter() { - models = new ArrayList<>(); - } - - public void clearAndAddAll(Collection data) { - models.clear(); - presenters.clear(); - - for (M item : data) { - addInternal(item); - } - - notifyDataSetChanged(); - } - - public void addAll(Collection data) { - for (M item : data) { - addInternal(item); - } - - int addedSize = data.size(); - int oldSize = models.size() - addedSize; - notifyItemRangeInserted(oldSize, addedSize); - } - - public void addItem(M item) { - addInternal(item); - notifyItemInserted(models.size()); - } - - public void updateItem(M item) { - Object modelId = getModelId(item); - - // Swap the model - int position = getItemPosition(item); - if (position >= 0) { - models.remove(position); - models.add(position, item); - } - - // Swap the presenter - P existingPresenter = presenters.get(modelId); - if (existingPresenter != null) { - existingPresenter.setModel(item); - } - - if (position >= 0) { - notifyItemChanged(position); - } - } - - public void removeItem(M item) { - int position = getItemPosition(item); - if (position >= 0) { - models.remove(item); - } - presenters.remove(getModelId(item)); - - if (position >= 0) { - notifyItemRemoved(position); - } - } - - private int getItemPosition(M item) { - Object modelId = getModelId(item); - - int position = -1; - for (int i = 0; i < models.size(); i++) { - M model = models.get(i); - if (getModelId(model).equals(modelId)) { - position = i; - break; - } - } - return position; - } - - private void addInternal(M item) { - System.err.println("Adding item " + getModelId(item)); - models.add(item); - presenters.put(getModelId(item), createPresenter(item)); - } - - @Override - public int getItemCount() { - return models.size(); - } - - @Override - protected M getItem(int position) { - return models.get(position); - } -} diff --git a/app/src/main/java/com/remind101/archexample/MvpViewHolder.java b/app/src/main/java/com/remind101/archexample/MvpViewHolder.java deleted file mode 100644 index 5cbef44..0000000 --- a/app/src/main/java/com/remind101/archexample/MvpViewHolder.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.remind101.archexample; - -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import com.remind101.archexample.presenters.BasePresenter; - -public abstract class MvpViewHolder

extends RecyclerView.ViewHolder { - protected P presenter; - - public MvpViewHolder(View itemView) { - super(itemView); - } - - public void bindPresenter(P presenter) { - this.presenter = presenter; - presenter.bindView(this); - } - - public void unbindPresenter() { - presenter = null; - } -} diff --git a/app/src/main/java/com/remind101/archexample/PresenterManager.java b/app/src/main/java/com/remind101/archexample/PresenterManager.java deleted file mode 100644 index cf4fdce..0000000 --- a/app/src/main/java/com/remind101/archexample/PresenterManager.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.remind101.archexample; - -import android.os.Bundle; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.remind101.archexample.presenters.BasePresenter; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -public class PresenterManager { - private static final String SIS_KEY_PRESENTER_ID = "presenter_id"; - private static PresenterManager instance; - - private final AtomicLong currentId; - - private final Cache> presenters; - - PresenterManager(long maxSize, long expirationValue, TimeUnit expirationUnit) { - currentId = new AtomicLong(); - - presenters = CacheBuilder.newBuilder() - .maximumSize(maxSize) - .expireAfterWrite(expirationValue, expirationUnit) - .build(); - } - - public static PresenterManager getInstance() { - if (instance == null) { - instance = new PresenterManager(10, 30, TimeUnit.SECONDS); - } - return instance; - } - - public

> P restorePresenter(Bundle savedInstanceState) { - Long presenterId = savedInstanceState.getLong(SIS_KEY_PRESENTER_ID); - P presenter = (P) presenters.getIfPresent(presenterId); - presenters.invalidate(presenterId); - return presenter; - } - - public void savePresenter(BasePresenter presenter, Bundle outState) { - long presenterId = currentId.incrementAndGet(); - presenters.put(presenterId, presenter); - outState.putLong(SIS_KEY_PRESENTER_ID, presenterId); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/remind101/archexample/Utils.java b/app/src/main/java/com/remind101/archexample/Utils.java new file mode 100644 index 0000000..bed0183 --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/Utils.java @@ -0,0 +1,10 @@ +package com.remind101.archexample; + +import android.util.Log; + +public class Utils { + + public static void logIt(String message) { + Log.e("APP_TAG", message); + } +} diff --git a/app/src/main/java/com/remind101/archexample/presenters/BasePresenter.java b/app/src/main/java/com/remind101/archexample/presenters/BasePresenter.java deleted file mode 100644 index f97142c..0000000 --- a/app/src/main/java/com/remind101/archexample/presenters/BasePresenter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.remind101.archexample.presenters; - -import android.support.annotation.NonNull; - -import java.lang.ref.WeakReference; - -public abstract class BasePresenter { - protected M model; - private WeakReference view; - - public void setModel(M model) { - resetState(); - this.model = model; - if (setupDone()) { - updateView(); - } - } - - protected void resetState() { - } - - public void bindView(@NonNull V view) { - this.view = new WeakReference<>(view); - if (setupDone()) { - updateView(); - } - } - - public void unbindView() { - this.view = null; - } - - protected V view() { - if (view == null) { - return null; - } else { - return view.get(); - } - } - - protected abstract void updateView(); - - protected boolean setupDone() { - return view() != null && model != null; - } -} diff --git a/app/src/main/java/com/remind101/archexample/presenters/CounterPresenter.java b/app/src/main/java/com/remind101/archexample/presenters/CounterPresenter.java deleted file mode 100644 index 6d92e8b..0000000 --- a/app/src/main/java/com/remind101/archexample/presenters/CounterPresenter.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.remind101.archexample.presenters; - -import com.remind101.archexample.CounterDatabase; -import com.remind101.archexample.models.Counter; -import com.remind101.archexample.views.CounterView; - -public class CounterPresenter extends BasePresenter { - private static final int MIN_VALUE = 0; - private static final int MAX_VALUE = 99; - - @Override - protected void updateView() { - view().setCounterName(model.getName()); - int value = model.getValue(); - view().setCounterValue(value); - view().setMinusButtonEnabled(value > MIN_VALUE); - view().setPlusButtonEnabled(value < MAX_VALUE); - } - - public void onMinusButtonClicked() { - if (setupDone() && model.getValue() > MIN_VALUE) { - model.setValue(model.getValue() - 1); - CounterDatabase.getInstance().saveCounter(model); - updateView(); - } - } - - public void onPlusButtonClicked() { - if (setupDone() && model.getValue() < MAX_VALUE) { - model.setValue(model.getValue() + 1); - CounterDatabase.getInstance().saveCounter(model); - updateView(); - } - } - - public void onCounterClicked() { - if (setupDone()) { - view().goToDetailView(model); - } - } -} diff --git a/app/src/main/java/com/remind101/archexample/views/MainView.java b/app/src/main/java/com/remind101/archexample/presenters/IMainView.java similarity index 50% rename from app/src/main/java/com/remind101/archexample/views/MainView.java rename to app/src/main/java/com/remind101/archexample/presenters/IMainView.java index 748ce34..17ca554 100644 --- a/app/src/main/java/com/remind101/archexample/views/MainView.java +++ b/app/src/main/java/com/remind101/archexample/presenters/IMainView.java @@ -1,13 +1,13 @@ -package com.remind101.archexample.views; +package com.remind101.archexample.presenters; +import com.arellomobile.mvp.MvpView; import com.remind101.archexample.models.Counter; import java.util.List; -public interface MainView { +public interface IMainView extends MvpView { void showCounters(List counters); - void showLoading(); - void showEmpty(); -} + void showMenu(boolean state); +} \ No newline at end of file diff --git a/app/src/main/java/com/remind101/archexample/presenters/MainPresenter.java b/app/src/main/java/com/remind101/archexample/presenters/MainPresenter.java index c2a0c78..df63928 100644 --- a/app/src/main/java/com/remind101/archexample/presenters/MainPresenter.java +++ b/app/src/main/java/com/remind101/archexample/presenters/MainPresenter.java @@ -1,51 +1,78 @@ -package com.remind101.archexample.presenters; +package com.remind101.archexample.moxy.presenter; import android.os.AsyncTask; import android.os.SystemClock; -import android.support.annotation.NonNull; +import com.arellomobile.mvp.InjectViewState; +import com.arellomobile.mvp.MvpPresenter; import com.remind101.archexample.CounterDatabase; import com.remind101.archexample.models.Counter; -import com.remind101.archexample.views.MainView; +import com.remind101.archexample.presenters.IMainView; import java.util.List; -public class MainPresenter extends BasePresenter, MainView> { +@InjectViewState +public class MainPresenter extends MvpPresenter { + private boolean isLoadingData = false; + private List model; @Override - protected void updateView() { - // Business logic is in the presenter - if (model.size() == 0) { - view().showEmpty(); - } else { - view().showCounters(model); + protected void onFirstViewAttach() { + super.onFirstViewAttach(); + + // Let's not reload data if it's already here + if (model == null && !isLoadingData) { + getViewState().showLoading(); + loadData(); } } @Override - public void bindView(@NonNull MainView view) { - super.bindView(view); + public void attachView(IMainView view) { + super.attachView(view); - // Let's not reload data if it's already here if (model == null && !isLoadingData) { - view().showLoading(); + getViewState().showLoading(); loadData(); } } + private void setModel(List model) { + + this.model = model; + if (setupDone()) { + updateView(); + } + } + + private boolean setupDone() { + return model != null; + } + + private void updateView() { + if (model.size() == 0) { + getViewState().showEmpty(); + } else { + getViewState().showCounters(model); + } + } + private void loadData() { isLoadingData = true; new LoadDataTask().execute(); } + // ID нового Counter генерится здесь как очередной порядковый номер в массиве. + // По этому ID Counter также сохраняется к кэше. Это для того, чтобы поддерживать + // совпадение индекса в списке, индекса элемента в RecyclerView и ID в кэше. public void onAddCounterClicked() { Counter counter = new Counter(); - counter.setName("New Counter"); + counter.setId(model.size()); + counter.setName("New Counter " + model.size()); counter.setValue(0); - // Update view immediately - model.add(counter); + model.add(model.size(), counter); CounterDatabase.getInstance().saveCounter(counter); updateView(); } @@ -53,6 +80,13 @@ public void onAddCounterClicked() { // It's OK for this class not to be static and to keep a reference to the Presenter, as this // is retained during orientation changes and is lightweight (has no activity/view reference) private class LoadDataTask extends AsyncTask { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + getViewState().showMenu(false); + } + @Override protected Void doInBackground(Void... params) { SystemClock.sleep(3000); @@ -63,6 +97,7 @@ protected Void doInBackground(Void... params) { protected void onPostExecute(Void aVoid) { setModel(CounterDatabase.getInstance().getAllCounters()); isLoadingData = false; + getViewState().showMenu(true); } } } diff --git a/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterAdapter.java b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterAdapter.java new file mode 100644 index 0000000..0ac294f --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterAdapter.java @@ -0,0 +1,58 @@ +package com.remind101.archexample.recyclerview_with_moxy; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import com.arellomobile.mvp.MvpDelegate; +import com.remind101.archexample.R; +import com.remind101.archexample.models.Counter; +import com.remind101.archexample.recyclerview_with_moxy.CounterViewHolder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class CounterAdapter extends RecyclerView.Adapter { + + protected final List models; + + private MvpDelegate mParentDelegate; + + public CounterAdapter(MvpDelegate mParentDelegate) { + this.mParentDelegate = mParentDelegate; + models = new ArrayList<>(); + } + + // Списку требуется создать новый элемент + @NonNull + public CounterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new CounterViewHolder( + mParentDelegate, + LayoutInflater + .from(parent.getContext()).inflate(R.layout.counter_row, + parent, + false)); + } + + // Списку нужно заполнить данными элемент в позиции position. в Holder + // передаем только position. По этому знаению его Presenter найдет + // нужный Counter + @Override + public void onBindViewHolder(CounterViewHolder holder, int position) { + holder.bindPosition(position); + } + + @Override + public int getItemCount() { + return models.size(); + } + + // Обновить все содержимое списка + public void clearAndAddAll(Collection data) { + models.clear(); + models.addAll(data); + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterPresenter.java b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterPresenter.java new file mode 100644 index 0000000..4c04197 --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterPresenter.java @@ -0,0 +1,67 @@ +package com.remind101.archexample.recyclerview_with_moxy; + +import com.arellomobile.mvp.InjectViewState; +import com.arellomobile.mvp.MvpPresenter; +import com.remind101.archexample.CounterDatabase; +import com.remind101.archexample.models.Counter; + +@InjectViewState +public class CounterPresenter extends MvpPresenter { + private static final int MIN_VALUE = 0; + private static final int MAX_VALUE = 5; + private Counter model; + + private void updateView(int value) { + try { + getViewState().setMinusButtonEnabled(value > MIN_VALUE); + getViewState().setPlusButtonEnabled(value < MAX_VALUE); + getViewState().setCounterValue(value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void onMinusButtonClicked(int position) { + try { + model = CounterDatabase.getInstance().getCounter(position); + int value = model.getValue(); + + if(value > MIN_VALUE) { + model.setValue(--value); + CounterDatabase.getInstance().saveCounter(model); + updateView(value); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void onPlusButtonClicked(int position) { + try { + model = CounterDatabase.getInstance().getCounter(position); + int value = model.getValue(); + + if(value < MAX_VALUE) { + model.setValue(++value); + CounterDatabase.getInstance().saveCounter(model); + updateView(value); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Moxy-методы onFirstViewAttach() и attachView(View view) + * вызываются раньше и position для них не актуальна, поэтому + * там её не нужно использовать + */ + + public void onBindPosition(int position) { + + model = CounterDatabase.getInstance().getCounter(position); + getViewState().setCounterName(model.getName()); + updateView(model.getValue()); + } +} diff --git a/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterView.java b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterView.java new file mode 100644 index 0000000..0e5ddf3 --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterView.java @@ -0,0 +1,19 @@ +package com.remind101.archexample.recyclerview_with_moxy; + +import com.arellomobile.mvp.MvpView; +import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy; +import com.arellomobile.mvp.viewstate.strategy.SingleStateStrategy; +import com.arellomobile.mvp.viewstate.strategy.SkipStrategy; +import com.arellomobile.mvp.viewstate.strategy.StateStrategyType; + +public interface CounterView extends MvpView { + @StateStrategyType(SkipStrategy.class) + void setCounterName(String name); + @StateStrategyType(SkipStrategy.class) + void setCounterValue(int value); + + @StateStrategyType(SkipStrategy.class) + void setMinusButtonEnabled(boolean enabled); + @StateStrategyType(SkipStrategy.class) + void setPlusButtonEnabled(boolean enabled); +} diff --git a/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterViewHolder.java b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterViewHolder.java new file mode 100644 index 0000000..3e140d6 --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/CounterViewHolder.java @@ -0,0 +1,94 @@ +package com.remind101.archexample.recyclerview_with_moxy; + +import android.graphics.Color; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.arellomobile.mvp.MvpDelegate; +import com.arellomobile.mvp.presenter.InjectPresenter; +import com.arellomobile.mvp.presenter.PresenterType; +import com.arellomobile.mvp.presenter.ProvidePresenterTag; +import com.remind101.archexample.R; + +public class CounterViewHolder extends MvpViewHolder implements CounterView { + private final View listItemView; + private final TextView counterName; + private final TextView counterValue; + private final ImageView minusButton; + private final ImageView plusButton; + + private int position = 0; + + @InjectPresenter(type = PresenterType.GLOBAL) + public CounterPresenter presenter; + + /** + * Требуется для того чтобы сгенерировать уникальный тэг для презентера данного MvpView, + * то есть данного холдера. Если этого не сделать, то у всех холдеров будет один и тот же инстанс + * презентера и он будет ОДНОВРЕМЕННО обновлять их все ОДИНАКОВЫМИ данными )) + * + * https://github.com/Arello-Mobile/Moxy/wiki/Provides-Presenter-and-its-Tag + */ + @ProvidePresenterTag(presenterClass = CounterPresenter.class, type = PresenterType.GLOBAL) + String provideRepositoryPresenterTag() { + return Long.toString((long)hashCode() + System.currentTimeMillis()); + } + + public CounterViewHolder(MvpDelegate mParentDelegate, View itemView) { + super(mParentDelegate, itemView); + + listItemView = itemView; + counterName = itemView.findViewById(R.id.counter_name); + counterValue = itemView.findViewById(R.id.counter_value); + minusButton = itemView.findViewById(R.id.minus_button); + plusButton = itemView.findViewById(R.id.plus_button); + + getMvpDelegate().onCreate(); + getMvpDelegate().onAttach(); + } + + @Override + public void setCounterName(String name) { + counterName.setText(name); + } + + @Override + public void setCounterValue(int value) { + counterValue.setText(String.valueOf(value)); + } + + @Override + public void setMinusButtonEnabled(boolean enabled) { + minusButton.getDrawable().setTint(enabled ? Color.BLACK : Color.GRAY); + minusButton.setClickable(enabled); + } + + @Override + public void setPlusButtonEnabled(boolean enabled) { + plusButton.getDrawable().setTint(enabled ? Color.BLACK : Color.GRAY); + plusButton.setClickable(enabled); + } + + public void bindPosition(int position) { +// destroyMvpDelegate(); +// createMvpDelegate(); + + this.position = position; + presenter.onBindPosition(position); + + minusButton.setOnClickListener( view -> { + presenter.onMinusButtonClicked(position); + }); + + plusButton.setOnClickListener( view -> { + presenter.onPlusButtonClicked(position); + }); + } + + // Critical! Return this item unique id + @Override + protected String getMvpChildId() { + return listItemView == null ? null : Integer.toString(listItemView.getId()); + } +} diff --git a/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/MvpViewHolder.java b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/MvpViewHolder.java new file mode 100644 index 0000000..6223602 --- /dev/null +++ b/app/src/main/java/com/remind101/archexample/recyclerview_with_moxy/MvpViewHolder.java @@ -0,0 +1,54 @@ +/** + * Materials: + * https://gist.github.com/senneco/ef2910a5b53aacdb053ebca21b10ef77 + * + * and + * + * https://gist.github.com/AlexeyKorshun/abb20751b23d274aac86a63108094866 + */ + +package com.remind101.archexample.recyclerview_with_moxy; + +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.arellomobile.mvp.MvpDelegate; + +public abstract class MvpViewHolder extends RecyclerView.ViewHolder { + + protected MvpDelegate mMvpDelegate; + protected MvpDelegate mParentDelegate; + + public MvpViewHolder(MvpDelegate parentDelegate, final View itemView) { + super(itemView); + mParentDelegate = parentDelegate; + } + + @Nullable + protected MvpDelegate getMvpDelegate() { + if (mMvpDelegate == null && getMvpChildId() != null) { + mMvpDelegate = new MvpDelegate(this); + mMvpDelegate.setParentDelegate(mParentDelegate, getMvpChildId()); + } + return mMvpDelegate; + } + + protected void destroyMvpDelegate() { + if (getMvpDelegate() != null) { + getMvpDelegate().onSaveInstanceState(); + getMvpDelegate().onDetach(); + mMvpDelegate = null; + } + } + + protected void createMvpDelegate() { + if (getMvpDelegate() != null) { + getMvpDelegate().onCreate(); + getMvpDelegate().onAttach(); + getMvpDelegate().onSaveInstanceState(); + } + } + + protected abstract String getMvpChildId(); +} diff --git a/app/src/main/java/com/remind101/archexample/views/CounterView.java b/app/src/main/java/com/remind101/archexample/views/CounterView.java deleted file mode 100644 index a539d75..0000000 --- a/app/src/main/java/com/remind101/archexample/views/CounterView.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.remind101.archexample.views; - -import com.remind101.archexample.models.Counter; - -public interface CounterView { - void setCounterName(String name); - - void setCounterValue(int value); - - void setMinusButtonEnabled(boolean enabled); - - void setPlusButtonEnabled(boolean enabled); - - void goToDetailView(Counter counter); -} diff --git a/app/src/main/java/com/remind101/archexample/views/CounterViewHolder.java b/app/src/main/java/com/remind101/archexample/views/CounterViewHolder.java deleted file mode 100644 index 8de4d5c..0000000 --- a/app/src/main/java/com/remind101/archexample/views/CounterViewHolder.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.remind101.archexample.views; - -import android.graphics.Color; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.remind101.archexample.MvpViewHolder; -import com.remind101.archexample.R; -import com.remind101.archexample.models.Counter; -import com.remind101.archexample.presenters.CounterPresenter; - -public class CounterViewHolder extends MvpViewHolder implements CounterView { - private final TextView counterName; - private final TextView counterValue; - private final ImageView minusButton; - private final ImageView plusButton; - @Nullable private OnCounterClickListener listener; - - public CounterViewHolder(View itemView) { - super(itemView); - counterName = (TextView) itemView.findViewById(R.id.counter_name); - counterValue = (TextView) itemView.findViewById(R.id.counter_value); - minusButton = (ImageView) itemView.findViewById(R.id.minus_button); - plusButton = (ImageView) itemView.findViewById(R.id.plus_button); - - minusButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - presenter.onMinusButtonClicked(); - } - }); - plusButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - presenter.onPlusButtonClicked(); - } - }); - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - presenter.onCounterClicked(); - } - }); - } - - public void setListener(@Nullable OnCounterClickListener listener) { - this.listener = listener; - } - - @Override - public void setCounterName(String name) { - counterName.setText(name); - } - - @Override - public void setCounterValue(int value) { - counterValue.setText(String.valueOf(value)); - } - - @Override - public void setMinusButtonEnabled(boolean enabled) { - minusButton.getDrawable().setTint(enabled ? Color.BLACK : Color.GRAY); - minusButton.setClickable(enabled); - } - - @Override - public void setPlusButtonEnabled(boolean enabled) { - plusButton.getDrawable().setTint(enabled ? Color.BLACK : Color.GRAY); - plusButton.setClickable(enabled); - } - - @Override - public void goToDetailView(Counter counter) { - if (listener != null) { - listener.onCounterClick(counter); - } - } - - public interface OnCounterClickListener { - void onCounterClick(Counter counter); - } -} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..84f1951 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index cde69bc..898f3ed 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..dffca36 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index c133a0c..64ba76f 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..dae5e08 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index bfa42f0..e5ed465 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..14ed0af Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 324e72c..b0907ca 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index aee44e1..2c18de9 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..beed3cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816..0000000 --- a/app/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/app/src/test/java/com/remind101/archexample/ExampleUnitTest.java b/app/src/test/java/com/remind101/archexample/ExampleUnitTest.java new file mode 100644 index 0000000..7114fc1 --- /dev/null +++ b/app/src/test/java/com/remind101/archexample/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.remind101.archexample; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/remind101/archexample/presenters/CounterPresenterTest.java b/app/src/test/java/com/remind101/archexample/presenters/CounterPresenterTest.java deleted file mode 100644 index dee387a..0000000 --- a/app/src/test/java/com/remind101/archexample/presenters/CounterPresenterTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.remind101.archexample.presenters; - -import com.remind101.archexample.models.Counter; -import com.remind101.archexample.views.CounterView; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -public class CounterPresenterTest { - private CounterPresenter presenter; - private CounterView view; - private Counter counter; - - @Before - public void setup() { - presenter = spy(new CounterPresenter()); - view = mock(CounterView.class); - presenter.bindView(view); - counter = new Counter(); - counter.setId(4); - counter.setName("My Counter"); - counter.setValue(18); - } - - @Test - public void testUpdateView_setsName() { - presenter.setModel(counter); - - verify(view).setCounterName(eq("My Counter")); - } - - @Test - public void testUpdateView_setsValue() { - presenter.setModel(counter); - - verify(view).setCounterValue(eq(18)); - } - - @Test - public void testUpdateView_whenCounterGreaterThan0_setsMinusButtonEnabled() { - presenter.setModel(counter); - - verify(view).setMinusButtonEnabled(eq(true)); - } - - @Test - public void testUpdateView_whenCounterEqual0_setsMinusButtonDisabled() { - counter.setValue(0); - presenter.setModel(counter); - - verify(view).setMinusButtonEnabled(eq(false)); - } - - @Test - public void testUpdateView_whenCounterLowerThan99_setsPlusButtonEnabled() { - presenter.setModel(counter); - - verify(view).setPlusButtonEnabled(eq(true)); - } - - @Test - public void testUpdateView_whenCounterEqual99_setsMinusButtonDisabled() { - counter.setValue(99); - presenter.setModel(counter); - - verify(view).setPlusButtonEnabled(eq(false)); - } - - @Test - public void testOnMinusButtonClicked_whenCounterGreaterThan0_decrementsValue() { - counter.setValue(16); - presenter.setModel(counter); - reset(view); - - presenter.onMinusButtonClicked(); - assertEquals(counter.getValue(), 15); - } - - @Test - public void testOnMinusButtonClicked_whenCounterEquals0_doesNotDoAnything() { - counter.setValue(0); - presenter.setModel(counter); - reset(view); - - presenter.onMinusButtonClicked(); - assertEquals(counter.getValue(), 0); - } - - @Test - public void testOnPlusButtonClicked_whenCounterLowerThan99_incrementsValue() { - counter.setValue(16); - presenter.setModel(counter); - reset(view); - - presenter.onPlusButtonClicked(); - assertEquals(counter.getValue(), 17); - } - - @Test - public void testOnPlusButtonClicked_whenCounterEquals99_doesNotDoAnything() { - counter.setValue(99); - presenter.setModel(counter); - reset(view); - - presenter.onPlusButtonClicked(); - assertEquals(counter.getValue(), 99); - } - - @Test - public void testOnCounterClicked_opensDetailView() { - presenter.setModel(counter); - reset(view); - - presenter.onCounterClicked(); - verify(view).goToDetailView(eq(counter)); - } -} diff --git a/build.gradle b/build.gradle index e0b366a..66145ac 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,14 @@ buildscript { repositories { + google() jcenter() + mavenCentral() + } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - + classpath 'com.android.tools.build:gradle:3.4.1' + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -14,7 +17,9 @@ buildscript { allprojects { repositories { + google() jcenter() + } } diff --git a/gradle.properties b/gradle.properties index 1d3591c..82618ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,15 @@ # 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 - +org.gradle.jvmargs=-Xmx1536m # 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 \ No newline at end of file +# org.gradle.parallel=true + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d9c501 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Oct 22 18:35:20 MSK 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew index 91a7e26..cccdd3d 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# 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 APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# 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\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi 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` @@ -154,11 +154,19 @@ if $cygwin ; then 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=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @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 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= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows 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. @@ -60,11 +59,6 @@ set _SKIP=2 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