diff --git a/T31-Load/ToDo/.gitignore b/T31-Load/ToDo/.gitignore new file mode 100644 index 0000000..09b993d --- /dev/null +++ b/T31-Load/ToDo/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/T31-Load/ToDo/app/.gitignore b/T31-Load/ToDo/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/T31-Load/ToDo/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/T31-Load/ToDo/app/build.gradle b/T31-Load/ToDo/app/build.gradle new file mode 100644 index 0000000..c7018e6 --- /dev/null +++ b/T31-Load/ToDo/app/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.commonsware.todo" + minSdkVersion 21 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testApplicationId "com.commonsware.todo.test" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + dataBinding { + enabled=true + } +} + +def supportVer="27.1.1" +def autoValueVer="1.5.1" +def archVer="1.1.1" +def roomVer="1.1.0" + +dependencies { + implementation "com.android.support:recyclerview-v7:$supportVer" + implementation "com.android.support:support-fragment:$supportVer" + implementation "android.arch.lifecycle:extensions:$archVer" + implementation "android.arch.lifecycle:reactivestreams:$archVer" + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation "io.reactivex.rxjava2:rxjava:2.1.7" + implementation "android.arch.persistence.room:runtime:$roomVer" + implementation "android.arch.persistence.room:rxjava2:$roomVer" + annotationProcessor "android.arch.persistence.room:compiler:$roomVer" + compileOnly "com.google.auto.value:auto-value:$autoValueVer" + annotationProcessor "com.google.auto.value:auto-value:$autoValueVer" + 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/T31-Load/ToDo/app/proguard-rules.pro b/T31-Load/ToDo/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/T31-Load/ToDo/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java b/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java new file mode 100644 index 0000000..870fafe --- /dev/null +++ b/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java @@ -0,0 +1,102 @@ +package com.commonsware.todo; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import io.reactivex.subjects.PublishSubject; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class ControllerTest { + @Before + public void setUp() { + ToDoDatabase db=ToDoDatabase.get(InstrumentationRegistry.getTargetContext()); + + db.todoStore().deleteAll(); + } + + @Test + public void controller() throws InterruptedException { + final Controller controller=new Controller(InstrumentationRegistry.getTargetContext()); + final PublishSubject actionSubject=PublishSubject.create(); + final LinkedBlockingQueue receivedResults=new LinkedBlockingQueue<>(); + + controller.subscribeToActions(actionSubject); + controller.resultStream().subscribe(receivedResults::offer); + + actionSubject.onNext(Action.load()); + + Result.Loaded resultLoaded= + (Result.Loaded)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(0, resultLoaded.models().size()); + + final ToDoModel fooModel=ToDoModel.creator().description("foo").notes("hello, world!").build(); + + actionSubject.onNext(Action.add(fooModel)); + + Result.Added resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(fooModel, resultAdded.model()); + + final ToDoModel barModel=ToDoModel.creator().description("bar").build(); + actionSubject.onNext(Action.add(barModel)); + resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(barModel, resultAdded.model()); + + final ToDoModel gooModel=ToDoModel.creator() + .description("goo") + .isCompleted(true) + .build(); + actionSubject.onNext(Action.add(gooModel)); + resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(gooModel, resultAdded.model()); + + final ToDoModel mutatedFoo=fooModel.toBuilder().isCompleted(true).build(); + actionSubject.onNext(Action.edit(mutatedFoo)); + + Result.Modified resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(mutatedFoo, resultModified.model()); + + final ToDoModel mutatedBar=barModel.toBuilder().description("bar!").notes("hi!").build(); + actionSubject.onNext(Action.edit(mutatedBar)); + resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(mutatedBar, resultModified.model()); + + final ToDoModel mutatedGoo=gooModel.toBuilder() + .description("goo!") + .isCompleted(false) + .build(); + actionSubject.onNext(Action.edit(mutatedGoo)); + resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(mutatedGoo, resultModified.model()); + + actionSubject.onNext(Action.delete(barModel)); + + Result.Deleted resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(barModel, resultDeleted.model()); + + actionSubject.onNext(Action.delete(fooModel)); + resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(fooModel, resultDeleted.model()); + + actionSubject.onNext(Action.delete(gooModel)); + resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(gooModel, resultDeleted.model()); + } +} \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java b/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java new file mode 100644 index 0000000..2b253d1 --- /dev/null +++ b/T31-Load/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java @@ -0,0 +1,79 @@ +package com.commonsware.todo; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.List; +import static org.hamcrest.Matchers.hasItem; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +@RunWith(AndroidJUnit4.class) +public class RepoTests { + private ToDoRepository repo; + + @Before + public void setUp() { + ToDoDatabase db=ToDoDatabase.get(InstrumentationRegistry.getTargetContext()); + + db.todoStore().deleteAll(); + repo=ToDoRepository.get(InstrumentationRegistry.getTargetContext()); + repo.add(ToDoModel.creator() + .description("Buy a copy of _Exploring Android_") + .notes("See https://wares.commonsware.com") + .isCompleted(true) + .build()); + repo.add(ToDoModel.creator() + .description("Complete all of the tutorials") + .build()); + repo.add(ToDoModel.creator() + .description("Write an app for somebody in my community") + .notes("Talk to some people at non-profit organizations to see what they need!") + .build()); + } + + @Test + public void getAll() { + assertEquals(3, repo.all().size()); + } + + @Test + public void add() { + ToDoModel model=ToDoModel.creator() + .isCompleted(true) + .description("foo") + .build(); + + repo.add(model); + + List models=repo.all(); + + assertEquals(4, models.size()); + assertThat(models, hasItem(model)); + } + + @Test + public void replace() { + ToDoModel original=repo.all().get(1); + ToDoModel edited=original.toBuilder() + .isCompleted(true) + .description("Currently on Tutorial #30") + .build(); + + repo.replace(edited); + assertEquals(3, repo.all().size()); + assertEquals(edited, repo.all().get(1)); + } + + @Test + public void delete() { + assertEquals(3, repo.all().size()); + repo.delete(repo.all().get(0)); + assertEquals(2, repo.all().size()); + repo.delete(repo.all().get(0).toBuilder().build()); + assertEquals(1, repo.all().size()); + } +} diff --git a/T31-Load/ToDo/app/src/main/AndroidManifest.xml b/T31-Load/ToDo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..27d8151 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/assets/about.html b/T31-Load/ToDo/app/src/main/assets/about.html new file mode 100644 index 0000000..56eb606 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/assets/about.html @@ -0,0 +1,17 @@ +

About This App

+ +

This app is cool!

+ +

No, really — this app is awesome!

+ +
+ . +
+ . +
+ . +
+ . +
+ +

OK, this app isn't all that much. But, hey, it's mine!

\ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java new file mode 100644 index 0000000..881c65c --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java @@ -0,0 +1,18 @@ +package com.commonsware.todo; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.webkit.WebView; + +public class AboutActivity extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + + WebView wv=findViewById(R.id.about); + + wv.loadUrl("file:///android_asset/about.html"); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Action.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Action.java new file mode 100644 index 0000000..643c069 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Action.java @@ -0,0 +1,49 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; + +public abstract class Action { + public static Action add(ToDoModel model) { + return new AutoValue_Action_Add(model); + } + + public static Action edit(ToDoModel model) { + return new AutoValue_Action_Edit(model); + } + + public static Action delete(ToDoModel model) { + return new AutoValue_Action_Delete(model); + } + + public static Action show(ToDoModel model) { + return new AutoValue_Action_Show(model); + } + + public static Action load() { + return new Action.Load(); + } + + @AutoValue + public static abstract class Add extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Edit extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Delete extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + static abstract class Show extends Action { + public abstract ToDoModel current(); + } + + public static class Load extends Action { + + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Controller.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Controller.java new file mode 100644 index 0000000..82cc75d --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Controller.java @@ -0,0 +1,67 @@ +package com.commonsware.todo; + +import android.content.Context; +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; + +public class Controller { + private final ToDoRepository toDoRepo; + private final PublishSubject resultSubject= + PublishSubject.create(); + + public Controller(Context ctxt) { + toDoRepo=ToDoRepository.get(ctxt); + } + + public void subscribeToActions(Observable actionStream) { + actionStream + .observeOn(Schedulers.io()) + .subscribe(this::processImpl); + } + + public Observable resultStream() { + return resultSubject; + } + + private void processImpl(Action action) { + if (action instanceof Action.Add) { + add(((Action.Add)action).model()); + } + else if (action instanceof Action.Edit) { + modify(((Action.Edit)action).model()); + } + else if (action instanceof Action.Delete) { + delete(((Action.Delete)action).model()); + } + else if (action instanceof Action.Load) { + load(); + } + else if (action instanceof Action.Show) { + show(((Action.Show)action).current()); + } + } + + private void add(ToDoModel model) { + toDoRepo.add(model); + resultSubject.onNext(Result.added(model)); + } + + private void modify(ToDoModel model) { + toDoRepo.replace(model); + resultSubject.onNext(Result.modified(model)); + } + + private void delete(ToDoModel toDelete) { + toDoRepo.delete(toDelete); + resultSubject.onNext(Result.deleted(toDelete)); + } + + private void load() { + resultSubject.onNext(Result.loaded(toDoRepo.all())); + } + + private void show(ToDoModel current) { + resultSubject.onNext(Result.showed(current)); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java new file mode 100644 index 0000000..d9a018f --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java @@ -0,0 +1,96 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoDisplayBinding; + +public class DisplayFragment extends Fragment { + interface Contract { + void editModel(ToDoModel model); + } + + private static final String ARG_ID="id"; + private TodoDisplayBinding binding; + private RosterViewModel viewModel; + + static DisplayFragment newInstance(ToDoModel model) { + DisplayFragment result=new DisplayFragment(); + + if (model!=null) { + Bundle args=new Bundle(); + + args.putString(ARG_ID, model.id()); + result.setArguments(args); + } + + return result; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_display, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.edit) { + ((Contract)getActivity()).editModel(binding.getModel()); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding=TodoDisplayBinding.inflate(getLayoutInflater(), container, false); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.stateStream().observe(this, this::render); + } + + private String getModelId() { + return getArguments().getString(ARG_ID); + } + + private void render(ViewState state) { + if (state!=null) { + ToDoModel model=state.current(); + + if (model!=null) { + binding.setModel(model); + binding.setCreatedOn(DateUtils.getRelativeDateTimeString(getActivity(), + model.createdOn().getTimeInMillis(), DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, 0)); + } + } + } +} \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java new file mode 100644 index 0000000..5ee6219 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java @@ -0,0 +1,135 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoEditBinding; + +public class EditFragment extends Fragment { + interface Contract { + void finishEdit(boolean deleted); + } + + private static final String ARG_ID="id"; + private TodoEditBinding binding; + private RosterViewModel viewModel; + private MenuItem deleteMenu; + + static EditFragment newInstance(ToDoModel model) { + EditFragment result=new EditFragment(); + + if (model!=null) { + Bundle args=new Bundle(); + + args.putString(ARG_ID, model.id()); + result.setArguments(args); + } + + return result; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_edit, menu); + deleteMenu=menu.findItem(R.id.delete); + deleteMenu.setVisible(getModelId()!=null); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.save) { + save(); + return true; + } + else if (item.getItemId()==R.id.delete) { + delete(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding=TodoEditBinding.inflate(getLayoutInflater(), container, false); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.stateStream().observe(this, this::render); + } + + private String getModelId() { + return getArguments()==null ? null : getArguments().getString(ARG_ID); + } + + private void render(ViewState state) { + if (state!=null) { + if (getModelId()==null) { + if (deleteMenu!=null) { + deleteMenu.setVisible(false); + } + } + else { + ToDoModel model=state.current(); + + binding.setModel(model); + } + } + } + + private void save() { + ToDoModel.Builder builder; + + if (binding.getModel()==null) { + builder=ToDoModel.creator(); + } + else { + builder=binding.getModel().toBuilder(); + } + + ToDoModel newModel=builder + .description(binding.desc.getText().toString()) + .notes(binding.notes.getText().toString()) + .isCompleted(binding.isCompleted.isChecked()) + .build(); + + if (binding.getModel()==null) { + viewModel.process(Action.add(newModel)); + } + else { + viewModel.process(Action.edit(newModel)); + } + + ((Contract)getActivity()).finishEdit(false); + } + + private void delete() { + viewModel.process(Action.delete(binding.getModel())); + ((Contract)getActivity()).finishEdit(true); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java new file mode 100644 index 0000000..9379863 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java @@ -0,0 +1,84 @@ +package com.commonsware.todo; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.inputmethod.InputMethodManager; + +public class MainActivity extends FragmentActivity + implements RosterListFragment.Contract, DisplayFragment.Contract, + EditFragment.Contract { + private static final String BACK_STACK_SHOW="showModel"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getSupportFragmentManager().findFragmentById(android.R.id.content)==null) { + getSupportFragmentManager().beginTransaction() + .add(android.R.id.content, new RosterListFragment()) + .commit(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.actions, menu); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.about) { + startActivity(new Intent(this, AboutActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void showModel(ToDoModel model) { + getSupportFragmentManager().beginTransaction() + .replace(android.R.id.content, DisplayFragment.newInstance(model)) + .addToBackStack(BACK_STACK_SHOW) + .commit(); + } + + @Override + public void editModel(ToDoModel model) { + getSupportFragmentManager().beginTransaction() + .replace(android.R.id.content, EditFragment.newInstance(model)) + .addToBackStack(null) + .commit(); + } + + @Override + public void addModel() { + editModel(null); + } + + @Override + public void finishEdit(boolean deleted) { + hideSoftInput(); + + if (deleted) { + getSupportFragmentManager().popBackStack(BACK_STACK_SHOW, + FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + else { + getSupportFragmentManager().popBackStack(); + } + } + + private void hideSoftInput() { + if (getCurrentFocus()!=null && getCurrentFocus().getWindowToken()!=null) { + ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); + } + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Result.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Result.java new file mode 100644 index 0000000..8d76e87 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/Result.java @@ -0,0 +1,52 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; +import java.util.Collections; +import java.util.List; + +public abstract class Result { + public static Result added(ToDoModel model) { + return new AutoValue_Result_Added(model); + } + + public static Result modified(ToDoModel model) { + return new AutoValue_Result_Modified(model); + } + + static Result deleted(ToDoModel model) { + return new AutoValue_Result_Deleted(model); + } + + static Result showed(ToDoModel current) { + return new AutoValue_Result_Showed(current); + } + + static Result loaded(List models) { + return new AutoValue_Result_Loaded(Collections.unmodifiableList(models)); + } + + @AutoValue + public static abstract class Added extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Modified extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Deleted extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + static abstract class Showed extends Result { + public abstract ToDoModel current(); + } + + @AutoValue + public static abstract class Loaded extends Result { + public abstract List models(); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java new file mode 100644 index 0000000..b079ea5 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java @@ -0,0 +1,46 @@ +package com.commonsware.todo; + +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoRowBinding; +import java.util.List; + +public class RosterListAdapter extends RecyclerView.Adapter { + private List models; + final private RosterListFragment host; + + RosterListAdapter(RosterListFragment host) { + this.host=host; + } + + @Override + public RosterRowHolder onCreateViewHolder(ViewGroup parent, int viewType) { + TodoRowBinding binding= + TodoRowBinding.inflate(host.getLayoutInflater(), parent, false); + + return new RosterRowHolder(binding, this); + } + + @Override + public void onBindViewHolder(RosterRowHolder holder, int position) { + holder.bind(models.get(position)); + } + + @Override + public int getItemCount() { + return models==null ? 0 : models.size(); + } + + public void replace(ToDoModel model, boolean isChecked) { + host.replace(model.toBuilder().isCompleted(isChecked).build()); + } + + void showModel(ToDoModel model) { + host.showModel(model); + } + + void setState(ViewState state) { + models=state.items(); + notifyDataSetChanged(); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java new file mode 100644 index 0000000..895ecac --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java @@ -0,0 +1,108 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +public class RosterListFragment extends Fragment { + interface Contract { + void showModel(ToDoModel model); + void addModel(); + } + + private RecyclerView rv; + private View empty, progress; + private RosterViewModel viewModel; + private RosterListAdapter adapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_roster, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.add) { + ((Contract)getActivity()).addModel(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View result=inflater.inflate(R.layout.todo_roster, container, false); + + rv=result.findViewById(R.id.items); + empty=result.findViewById(R.id.empty); + progress=result.findViewById(R.id.progressBar); + + return result; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + rv.setLayoutManager(new LinearLayoutManager(getActivity())); + + DividerItemDecoration decoration=new DividerItemDecoration(getActivity(), + LinearLayoutManager.VERTICAL); + + rv.addItemDecoration(decoration); + adapter=new RosterListAdapter(this); + rv.setAdapter(adapter); + viewModel.stateStream().observe(this, this::render); + } + + public void render(ViewState state) { + adapter.setState(state); + + if (state.isLoaded()) { + progress.setVisibility(View.GONE); + + if (adapter.getItemCount()==0) { + empty.setVisibility(View.VISIBLE); + } + else { + empty.setVisibility(View.GONE); + } + } + else { + progress.setVisibility(View.VISIBLE); + empty.setVisibility(View.GONE); + } + } + + void replace(ToDoModel model) { + viewModel.process(Action.edit(model)); + } + + void showModel(ToDoModel model) { + ((RosterListFragment.Contract)getActivity()).showModel(model); + viewModel.process(Action.show(model)); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java new file mode 100644 index 0000000..fc23476 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java @@ -0,0 +1,32 @@ +package com.commonsware.todo; + +import android.support.v7.widget.RecyclerView; +import com.commonsware.todo.databinding.TodoRowBinding; + +public class RosterRowHolder extends RecyclerView.ViewHolder { + final private TodoRowBinding binding; + private final RosterListAdapter adapter; + + public RosterRowHolder(TodoRowBinding binding, RosterListAdapter adapter) { + super(binding.getRoot()); + + this.binding=binding; + this.adapter=adapter; + } + + void bind(ToDoModel model) { + binding.setModel(model); + binding.setHolder(this); + binding.executePendingBindings(); + } + + public void completeChanged(ToDoModel model, boolean isChecked) { + if (model.isCompleted()!=isChecked) { + adapter.replace(model, isChecked); + } + } + + public void onClick() { + adapter.showModel(binding.getModel()); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java new file mode 100644 index 0000000..9cc35b0 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java @@ -0,0 +1,74 @@ +package com.commonsware.todo; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.LiveDataReactiveStreams; +import android.support.annotation.NonNull; +import java.util.List; +import io.reactivex.BackpressureStrategy; +import io.reactivex.functions.Consumer; +import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.ReplaySubject; + +public class RosterViewModel extends AndroidViewModel { + private LiveData states; + private ViewState lastState=ViewState.empty().build(); + private final PublishSubject actionSubject= + PublishSubject.create(); + private final ReplaySubject stateSubject=ReplaySubject.createWithSize(1); + + public RosterViewModel(@NonNull Application application) { + super(application); + + Controller controller=new Controller(application); + + controller.resultStream() + .subscribe(result -> { + lastState=foldResultIntoState(lastState, result); + stateSubject.onNext(lastState); + }, stateSubject::onError); + + states=LiveDataReactiveStreams + .fromPublisher(stateSubject.toFlowable(BackpressureStrategy.LATEST)); + + controller.subscribeToActions(actionSubject); + process(Action.load()); + } + + public LiveData stateStream() { + return states; + } + + public void process(Action action) { + actionSubject.onNext(action); + } + + private ViewState foldResultIntoState(@NonNull ViewState state, + @NonNull Result result) throws Exception { + if (result instanceof Result.Added) { + return state.add(((Result.Added)result).model()); + } + else if (result instanceof Result.Modified) { + return state.modify(((Result.Modified)result).model()); + } + else if (result instanceof Result.Deleted) { + return state.delete(((Result.Deleted)result).model()); + } + else if (result instanceof Result.Loaded) { + List models=((Result.Loaded)result).models(); + + return ViewState.builder() + .items(models) + .current(models.size()==0 ? null : models.get(0)) + .isLoaded(true) + .build(); + } + else if (result instanceof Result.Showed) { + return state.show(((Result.Showed)result).current()); + } + else { + throw new IllegalStateException("Unexpected result type: "+result.toString()); + } + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java new file mode 100644 index 0000000..e238840 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java @@ -0,0 +1,30 @@ +package com.commonsware.todo; + +import android.app.Application; +import android.os.Build; +import android.os.Handler; +import android.os.StrictMode; + +public class ToDoApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + new Handler().postAtFrontOfQueue(this::enableStrictMode); + } + + private void enableStrictMode() { + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + } + else { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java new file mode 100644 index 0000000..2e11288 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java @@ -0,0 +1,32 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.Room; +import android.arch.persistence.room.RoomDatabase; +import android.arch.persistence.room.TypeConverters; +import android.content.Context; + +@Database(entities={ToDoEntity.class}, version=1) +@TypeConverters({TypeTransmogrifier.class}) +public abstract class ToDoDatabase extends RoomDatabase { + public abstract ToDoEntity.Store todoStore(); + + private static final String DB_NAME="stuff.db"; + private static volatile ToDoDatabase INSTANCE=null; + + synchronized static ToDoDatabase get(Context ctxt) { + if (INSTANCE==null) { + INSTANCE=create(ctxt); + } + + return INSTANCE; + } + + private static ToDoDatabase create(Context ctxt) { + RoomDatabase.Builder b= + Room.databaseBuilder(ctxt.getApplicationContext(), ToDoDatabase.class, + DB_NAME); + + return b.build(); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java new file mode 100644 index 0000000..abad5af --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java @@ -0,0 +1,64 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Index; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.PrimaryKey; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.support.annotation.NonNull; +import java.util.Calendar; +import java.util.List; + +@Entity(tableName="todos", indices=@Index(value="id")) +public class ToDoEntity { + @PrimaryKey @NonNull final String id; + @NonNull final String description; + final String notes; + final boolean isCompleted; + @NonNull final Calendar createdOn; + + public static ToDoEntity fromModel(ToDoModel model) { + return new ToDoEntity(model.id(), model.description(), model.isCompleted(), + model.notes(), model.createdOn()); + } + + ToDoEntity(@NonNull String id, @NonNull String description, boolean isCompleted, + String notes, @NonNull Calendar createdOn) { + this.id=id; + this.description=description; + this.isCompleted=isCompleted; + this.notes=notes; + this.createdOn=createdOn; + } + + public ToDoModel toModel() { + return ToDoModel.builder() + .id(id) + .description(description) + .isCompleted(isCompleted) + .notes(notes) + .createdOn(createdOn) + .build(); + } + + @Dao + public interface Store { + @Query("SELECT * FROM todos ORDER BY description ASC") + List all(); + + @Insert + void insert(ToDoEntity... entities); + + @Update + void update(ToDoEntity... entities); + + @Delete + void delete(ToDoEntity... entities); + + @Query("DELETE FROM todos") + void deleteAll(); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java new file mode 100644 index 0000000..cec53fc --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java @@ -0,0 +1,49 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; +import java.util.Calendar; +import java.util.Comparator; +import java.util.UUID; +import android.support.annotation.Nullable; + +@AutoValue +public abstract class ToDoModel { + public abstract String id(); + public abstract boolean isCompleted(); + public abstract String description(); + @Nullable public abstract String notes(); + public abstract Calendar createdOn(); + + static Builder builder() { + return new AutoValue_ToDoModel.Builder(); + } + + public static Builder creator() { + return builder() + .isCompleted(false) + .id(UUID.randomUUID().toString()) + .createdOn(Calendar.getInstance()); + } + + public Builder toBuilder() { + return builder() + .id(id()) + .isCompleted(isCompleted()) + .description(description()) + .notes(notes()) + .createdOn(createdOn()); + } + + static final Comparator SORT_BY_DESC= + (one, two) -> (one.description().compareTo(two.description())); + + @AutoValue.Builder + public static abstract class Builder { + abstract Builder id(String id); + public abstract Builder isCompleted(boolean isCompleted); + public abstract Builder description(String desc); + public abstract Builder notes(String notes); + abstract Builder createdOn(Calendar date); + public abstract ToDoModel build(); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java new file mode 100644 index 0000000..27870b5 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java @@ -0,0 +1,45 @@ +package com.commonsware.todo; + +import android.content.Context; +import java.util.ArrayList; +import java.util.List; + +public class ToDoRepository { + private static volatile ToDoRepository INSTANCE=null; + private final ToDoDatabase db; + + public synchronized static ToDoRepository get(Context ctxt) { + if (INSTANCE==null) { + INSTANCE=new ToDoRepository(ctxt.getApplicationContext()); + } + + return INSTANCE; + } + + private ToDoRepository(Context ctxt) { + db=ToDoDatabase.get(ctxt); + } + + public List all() { + List entities=db.todoStore().all(); + ArrayList result=new ArrayList<>(entities.size()); + + for (ToDoEntity entity : entities) { + result.add(entity.toModel()); + } + + return result; + } + + public void add(ToDoModel model) { + db.todoStore().insert(ToDoEntity.fromModel(model)); + } + + public void replace(ToDoModel model) { + db.todoStore().update(ToDoEntity.fromModel(model)); + } + + public void delete(ToDoModel model) { + db.todoStore().delete(ToDoEntity.fromModel(model)); + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java new file mode 100644 index 0000000..b2a2ec2 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java @@ -0,0 +1,28 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.TypeConverter; +import java.util.Calendar; + +public class TypeTransmogrifier { + @TypeConverter + public static Long fromCalendar(Calendar date) { + if (date==null) { + return null; + } + + return date.getTimeInMillis(); + } + + @TypeConverter + public static Calendar toCalendar(Long millisSinceEpoch) { + if (millisSinceEpoch==null) { + return null; + } + + Calendar result=Calendar.getInstance(); + + result.setTimeInMillis(millisSinceEpoch); + + return result; + } +} diff --git a/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java new file mode 100644 index 0000000..d1b6a17 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java @@ -0,0 +1,110 @@ +package com.commonsware.todo; + +import android.support.annotation.Nullable; +import com.google.auto.value.AutoValue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@AutoValue +public abstract class ViewState { + public abstract List items(); + @Nullable public abstract ToDoModel current(); + public abstract boolean isLoaded(); + + static Builder builder() { + return new AutoValue_ViewState.Builder().isLoaded(false); + } + + static Builder empty() { + return builder().items(Collections.unmodifiableList(new ArrayList<>())); + } + + Builder toBuilder() { + return builder().items(items()).current(current()).isLoaded(isLoaded()); + } + + ViewState add(ToDoModel model) { + List models=new ArrayList<>(items()); + + models.add(model); + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(model) + .build(); + } + + ViewState modify(ToDoModel model) { + List models=new ArrayList<>(items()); + ToDoModel original=find(models, model.id()); + + if (original!=null) { + int index=models.indexOf(original); + models.set(index, model); + } + + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(model) + .build(); + } + + ViewState delete(ToDoModel model) { + List models=new ArrayList<>(items()); + ToDoModel original=find(models, model.id()); + + if (original==null) { + throw new IllegalArgumentException("Cannot find model to delete: "+model.toString()); + } + else { + models.remove(original); + } + + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(null) + .build(); + } + + ViewState show(ToDoModel current) { + return toBuilder() + .current(current) + .build(); + } + + private ToDoModel find(List models, String id) { + int position=findPosition(models, id); + + return position>=0 ? models.get(position) : null; + } + + private int findPosition(List models, String id) { + for (int i=0;i models) { + Collections.sort(models, ToDoModel.SORT_BY_DESC); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder items(List items); + abstract Builder current(ToDoModel current); + abstract Builder isLoaded(boolean isLoaded); + abstract ViewState build(); + } +} diff --git a/T31-Load/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/T31-Load/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..dfe5c75 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 0000000..0258249 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml new file mode 100644 index 0000000..1241eda --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml new file mode 100644 index 0000000..39e64d6 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml new file mode 100644 index 0000000..2ab2fb7 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml new file mode 100644 index 0000000..cf53e14 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_launcher_background.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..c975946 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/T31-Load/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml b/T31-Load/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml new file mode 100644 index 0000000..a561d63 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T31-Load/ToDo/app/src/main/res/layout/activity_about.xml b/T31-Load/ToDo/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..f86cabf --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/T31-Load/ToDo/app/src/main/res/layout/todo_display.xml b/T31-Load/ToDo/app/src/main/res/layout/todo_display.xml new file mode 100644 index 0000000..10f771f --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/layout/todo_display.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/layout/todo_edit.xml b/T31-Load/ToDo/app/src/main/res/layout/todo_edit.xml new file mode 100644 index 0000000..98ba52e --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/layout/todo_edit.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/layout/todo_roster.xml b/T31-Load/ToDo/app/src/main/res/layout/todo_roster.xml new file mode 100644 index 0000000..de28905 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/layout/todo_roster.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/T31-Load/ToDo/app/src/main/res/layout/todo_row.xml b/T31-Load/ToDo/app/src/main/res/layout/todo_row.xml new file mode 100644 index 0000000..09e37e2 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/layout/todo_row.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + diff --git a/T31-Load/ToDo/app/src/main/res/menu/actions.xml b/T31-Load/ToDo/app/src/main/res/menu/actions.xml new file mode 100644 index 0000000..791605e --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/menu/actions.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/menu/actions_display.xml b/T31-Load/ToDo/app/src/main/res/menu/actions_display.xml new file mode 100644 index 0000000..b1ae1a4 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/menu/actions_display.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/menu/actions_edit.xml b/T31-Load/ToDo/app/src/main/res/menu/actions_edit.xml new file mode 100644 index 0000000..6e53ed7 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/menu/actions_edit.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/menu/actions_roster.xml b/T31-Load/ToDo/app/src/main/res/menu/actions_roster.xml new file mode 100644 index 0000000..bec9cb2 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/menu/actions_roster.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..6f53cf8 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ca82f3a Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..6f53cf8 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..20094c0 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1295659 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..20094c0 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..296555b Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a436a78 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..296555b Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..904eb35 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ac1562e Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..904eb35 Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b1e8d5a Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8ce4e1b Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b1e8d5a Binary files /dev/null and b/T31-Load/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/T31-Load/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml b/T31-Load/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml new file mode 100644 index 0000000..2780f07 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml @@ -0,0 +1,11 @@ + + + #FFEB3B + #FBC02D + #FFF9C4 + #03a9f4 + #212121 + #757575 + #212121 + #BDBDBD + diff --git a/T31-Load/ToDo/app/src/main/res/values/dimens.xml b/T31-Load/ToDo/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..9055aab --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + 16sp + 48dp + 24sp + 16sp + 20sp + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/values/ic_launcher_background.xml b/T31-Load/ToDo/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..092305f --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F4D112 + \ No newline at end of file diff --git a/T31-Load/ToDo/app/src/main/res/values/strings.xml b/T31-Load/ToDo/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..93c7ec9 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/values/strings.xml @@ -0,0 +1,13 @@ + + ToDo + Click the + icon to add a todo item! + About + Item is completed + Created on: + Edit + Description + Notes + Save + Add + Delete + diff --git a/T31-Load/ToDo/app/src/main/res/values/styles.xml b/T31-Load/ToDo/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..32b7ff8 --- /dev/null +++ b/T31-Load/ToDo/app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/T31-Load/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java b/T31-Load/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java new file mode 100644 index 0000000..f4b5e4c --- /dev/null +++ b/T31-Load/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.commonsware.todo; + +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() throws Exception { + assertEquals(4, 2+2); + } +} \ No newline at end of file diff --git a/T31-Load/ToDo/build.gradle b/T31-Load/ToDo/build.gradle new file mode 100644 index 0000000..1a3d812 --- /dev/null +++ b/T31-Load/ToDo/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.2' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/T31-Load/ToDo/gradle.properties b/T31-Load/ToDo/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/T31-Load/ToDo/gradle.properties @@ -0,0 +1,17 @@ +# 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. +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 diff --git a/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.jar b/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.properties b/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..770a728 --- /dev/null +++ b/T31-Load/ToDo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Nov 14 13:31:09 EST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/T31-Load/ToDo/gradlew b/T31-Load/ToDo/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/T31-Load/ToDo/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/T31-Load/ToDo/gradlew.bat b/T31-Load/ToDo/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/T31-Load/ToDo/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/T31-Load/ToDo/settings.gradle b/T31-Load/ToDo/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/T31-Load/ToDo/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/T32-Filter/ToDo/.gitignore b/T32-Filter/ToDo/.gitignore new file mode 100644 index 0000000..09b993d --- /dev/null +++ b/T32-Filter/ToDo/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/T32-Filter/ToDo/app/.gitignore b/T32-Filter/ToDo/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/T32-Filter/ToDo/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/T32-Filter/ToDo/app/build.gradle b/T32-Filter/ToDo/app/build.gradle new file mode 100644 index 0000000..c7018e6 --- /dev/null +++ b/T32-Filter/ToDo/app/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.commonsware.todo" + minSdkVersion 21 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testApplicationId "com.commonsware.todo.test" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + dataBinding { + enabled=true + } +} + +def supportVer="27.1.1" +def autoValueVer="1.5.1" +def archVer="1.1.1" +def roomVer="1.1.0" + +dependencies { + implementation "com.android.support:recyclerview-v7:$supportVer" + implementation "com.android.support:support-fragment:$supportVer" + implementation "android.arch.lifecycle:extensions:$archVer" + implementation "android.arch.lifecycle:reactivestreams:$archVer" + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation "io.reactivex.rxjava2:rxjava:2.1.7" + implementation "android.arch.persistence.room:runtime:$roomVer" + implementation "android.arch.persistence.room:rxjava2:$roomVer" + annotationProcessor "android.arch.persistence.room:compiler:$roomVer" + compileOnly "com.google.auto.value:auto-value:$autoValueVer" + annotationProcessor "com.google.auto.value:auto-value:$autoValueVer" + 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/T32-Filter/ToDo/app/proguard-rules.pro b/T32-Filter/ToDo/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/T32-Filter/ToDo/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java b/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java new file mode 100644 index 0000000..9d676c2 --- /dev/null +++ b/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/ControllerTest.java @@ -0,0 +1,109 @@ +package com.commonsware.todo; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import io.reactivex.subjects.PublishSubject; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class ControllerTest { + @Before + public void setUp() { + ToDoDatabase db=ToDoDatabase.get(InstrumentationRegistry.getTargetContext()); + + db.todoStore().deleteAll(); + } + + @Test + public void controller() throws InterruptedException { + final Controller controller=new Controller(InstrumentationRegistry.getTargetContext()); + final PublishSubject actionSubject=PublishSubject.create(); + final LinkedBlockingQueue receivedResults=new LinkedBlockingQueue<>(); + + controller.subscribeToActions(actionSubject); + controller.resultStream().subscribe(receivedResults::offer); + + actionSubject.onNext(Action.load()); + + Result.Loaded resultLoaded= + (Result.Loaded)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(0, resultLoaded.models().size()); + + final ToDoModel fooModel=ToDoModel.creator().description("foo").notes("hello, world!").build(); + + actionSubject.onNext(Action.add(fooModel)); + + Result.Added resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(fooModel, resultAdded.model()); + + final ToDoModel barModel=ToDoModel.creator().description("bar").build(); + actionSubject.onNext(Action.add(barModel)); + resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(barModel, resultAdded.model()); + + final ToDoModel gooModel=ToDoModel.creator() + .description("goo") + .isCompleted(true) + .build(); + actionSubject.onNext(Action.add(gooModel)); + resultAdded= + (Result.Added)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(gooModel, resultAdded.model()); + + final ToDoModel mutatedFoo=fooModel.toBuilder().isCompleted(true).build(); + actionSubject.onNext(Action.edit(mutatedFoo)); + + Result.Modified resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(mutatedFoo, resultModified.model()); + + final ToDoModel mutatedBar=barModel.toBuilder().description("bar!").notes("hi!").build(); + actionSubject.onNext(Action.edit(mutatedBar)); + resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(mutatedBar, resultModified.model()); + + final ToDoModel mutatedGoo=gooModel.toBuilder() + .description("goo!") + .isCompleted(false) + .build(); + actionSubject.onNext(Action.edit(mutatedGoo)); + resultModified= + (Result.Modified)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(mutatedGoo, resultModified.model()); + + actionSubject.onNext(Action.delete(barModel)); + + Result.Deleted resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(barModel, resultDeleted.model()); + + actionSubject.onNext(Action.delete(fooModel)); + resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(fooModel, resultDeleted.model()); + + actionSubject.onNext(Action.delete(gooModel)); + resultDeleted= + (Result.Deleted)receivedResults.poll(1, TimeUnit.SECONDS); + assertEquals(gooModel, resultDeleted.model()); + + actionSubject.onNext(Action.filter(FilterMode.OUTSTANDING)); + + Result.Filtered resultFiltered= + (Result.Filtered)receivedResults.poll(1, TimeUnit.SECONDS); + + assertEquals(FilterMode.OUTSTANDING, resultFiltered.filterMode()); + } +} \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java b/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java new file mode 100644 index 0000000..2b253d1 --- /dev/null +++ b/T32-Filter/ToDo/app/src/androidTest/java/com/commonsware/todo/RepoTests.java @@ -0,0 +1,79 @@ +package com.commonsware.todo; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.List; +import static org.hamcrest.Matchers.hasItem; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +@RunWith(AndroidJUnit4.class) +public class RepoTests { + private ToDoRepository repo; + + @Before + public void setUp() { + ToDoDatabase db=ToDoDatabase.get(InstrumentationRegistry.getTargetContext()); + + db.todoStore().deleteAll(); + repo=ToDoRepository.get(InstrumentationRegistry.getTargetContext()); + repo.add(ToDoModel.creator() + .description("Buy a copy of _Exploring Android_") + .notes("See https://wares.commonsware.com") + .isCompleted(true) + .build()); + repo.add(ToDoModel.creator() + .description("Complete all of the tutorials") + .build()); + repo.add(ToDoModel.creator() + .description("Write an app for somebody in my community") + .notes("Talk to some people at non-profit organizations to see what they need!") + .build()); + } + + @Test + public void getAll() { + assertEquals(3, repo.all().size()); + } + + @Test + public void add() { + ToDoModel model=ToDoModel.creator() + .isCompleted(true) + .description("foo") + .build(); + + repo.add(model); + + List models=repo.all(); + + assertEquals(4, models.size()); + assertThat(models, hasItem(model)); + } + + @Test + public void replace() { + ToDoModel original=repo.all().get(1); + ToDoModel edited=original.toBuilder() + .isCompleted(true) + .description("Currently on Tutorial #30") + .build(); + + repo.replace(edited); + assertEquals(3, repo.all().size()); + assertEquals(edited, repo.all().get(1)); + } + + @Test + public void delete() { + assertEquals(3, repo.all().size()); + repo.delete(repo.all().get(0)); + assertEquals(2, repo.all().size()); + repo.delete(repo.all().get(0).toBuilder().build()); + assertEquals(1, repo.all().size()); + } +} diff --git a/T32-Filter/ToDo/app/src/main/AndroidManifest.xml b/T32-Filter/ToDo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..27d8151 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/assets/about.html b/T32-Filter/ToDo/app/src/main/assets/about.html new file mode 100644 index 0000000..56eb606 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/assets/about.html @@ -0,0 +1,17 @@ +

About This App

+ +

This app is cool!

+ +

No, really — this app is awesome!

+ +
+ . +
+ . +
+ . +
+ . +
+ +

OK, this app isn't all that much. But, hey, it's mine!

\ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java new file mode 100644 index 0000000..881c65c --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/AboutActivity.java @@ -0,0 +1,18 @@ +package com.commonsware.todo; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.webkit.WebView; + +public class AboutActivity extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + + WebView wv=findViewById(R.id.about); + + wv.loadUrl("file:///android_asset/about.html"); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Action.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Action.java new file mode 100644 index 0000000..c3880c4 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Action.java @@ -0,0 +1,58 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; + +public abstract class Action { + public static Action add(ToDoModel model) { + return new AutoValue_Action_Add(model); + } + + public static Action edit(ToDoModel model) { + return new AutoValue_Action_Edit(model); + } + + public static Action delete(ToDoModel model) { + return new AutoValue_Action_Delete(model); + } + + public static Action show(ToDoModel model) { + return new AutoValue_Action_Show(model); + } + + public static Action load() { + return new Action.Load(); + } + + public static Action filter(FilterMode mode) { + return(new AutoValue_Action_Filter(mode)); + } + + @AutoValue + public static abstract class Add extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Edit extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Delete extends Action { + public abstract ToDoModel model(); + } + + @AutoValue + static abstract class Show extends Action { + public abstract ToDoModel current(); + } + + public static class Load extends Action { + + } + + @AutoValue + static abstract class Filter extends Action { + public abstract FilterMode filterMode(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Controller.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Controller.java new file mode 100644 index 0000000..a387bed --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Controller.java @@ -0,0 +1,74 @@ +package com.commonsware.todo; + +import android.content.Context; +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; + +public class Controller { + private final ToDoRepository toDoRepo; + private final PublishSubject resultSubject= + PublishSubject.create(); + + public Controller(Context ctxt) { + toDoRepo=ToDoRepository.get(ctxt); + } + + public void subscribeToActions(Observable actionStream) { + actionStream + .observeOn(Schedulers.io()) + .subscribe(this::processImpl); + } + + public Observable resultStream() { + return resultSubject; + } + + private void processImpl(Action action) { + if (action instanceof Action.Add) { + add(((Action.Add)action).model()); + } + else if (action instanceof Action.Edit) { + modify(((Action.Edit)action).model()); + } + else if (action instanceof Action.Delete) { + delete(((Action.Delete)action).model()); + } + else if (action instanceof Action.Load) { + load(); + } + else if (action instanceof Action.Show) { + show(((Action.Show)action).current()); + } + else if (action instanceof Action.Filter) { + filter(((Action.Filter)action).filterMode()); + } + } + + private void add(ToDoModel model) { + toDoRepo.add(model); + resultSubject.onNext(Result.added(model)); + } + + private void modify(ToDoModel model) { + toDoRepo.replace(model); + resultSubject.onNext(Result.modified(model)); + } + + private void delete(ToDoModel toDelete) { + toDoRepo.delete(toDelete); + resultSubject.onNext(Result.deleted(toDelete)); + } + + private void load() { + resultSubject.onNext(Result.loaded(toDoRepo.all())); + } + + private void show(ToDoModel current) { + resultSubject.onNext(Result.showed(current)); + } + + private void filter(FilterMode mode) { + resultSubject.onNext(Result.filtered(mode)); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java new file mode 100644 index 0000000..d9a018f --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/DisplayFragment.java @@ -0,0 +1,96 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoDisplayBinding; + +public class DisplayFragment extends Fragment { + interface Contract { + void editModel(ToDoModel model); + } + + private static final String ARG_ID="id"; + private TodoDisplayBinding binding; + private RosterViewModel viewModel; + + static DisplayFragment newInstance(ToDoModel model) { + DisplayFragment result=new DisplayFragment(); + + if (model!=null) { + Bundle args=new Bundle(); + + args.putString(ARG_ID, model.id()); + result.setArguments(args); + } + + return result; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_display, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.edit) { + ((Contract)getActivity()).editModel(binding.getModel()); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding=TodoDisplayBinding.inflate(getLayoutInflater(), container, false); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.stateStream().observe(this, this::render); + } + + private String getModelId() { + return getArguments().getString(ARG_ID); + } + + private void render(ViewState state) { + if (state!=null) { + ToDoModel model=state.current(); + + if (model!=null) { + binding.setModel(model); + binding.setCreatedOn(DateUtils.getRelativeDateTimeString(getActivity(), + model.createdOn().getTimeInMillis(), DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, 0)); + } + } + } +} \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java new file mode 100644 index 0000000..5ee6219 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/EditFragment.java @@ -0,0 +1,135 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoEditBinding; + +public class EditFragment extends Fragment { + interface Contract { + void finishEdit(boolean deleted); + } + + private static final String ARG_ID="id"; + private TodoEditBinding binding; + private RosterViewModel viewModel; + private MenuItem deleteMenu; + + static EditFragment newInstance(ToDoModel model) { + EditFragment result=new EditFragment(); + + if (model!=null) { + Bundle args=new Bundle(); + + args.putString(ARG_ID, model.id()); + result.setArguments(args); + } + + return result; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_edit, menu); + deleteMenu=menu.findItem(R.id.delete); + deleteMenu.setVisible(getModelId()!=null); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.save) { + save(); + return true; + } + else if (item.getItemId()==R.id.delete) { + delete(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding=TodoEditBinding.inflate(getLayoutInflater(), container, false); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.stateStream().observe(this, this::render); + } + + private String getModelId() { + return getArguments()==null ? null : getArguments().getString(ARG_ID); + } + + private void render(ViewState state) { + if (state!=null) { + if (getModelId()==null) { + if (deleteMenu!=null) { + deleteMenu.setVisible(false); + } + } + else { + ToDoModel model=state.current(); + + binding.setModel(model); + } + } + } + + private void save() { + ToDoModel.Builder builder; + + if (binding.getModel()==null) { + builder=ToDoModel.creator(); + } + else { + builder=binding.getModel().toBuilder(); + } + + ToDoModel newModel=builder + .description(binding.desc.getText().toString()) + .notes(binding.notes.getText().toString()) + .isCompleted(binding.isCompleted.isChecked()) + .build(); + + if (binding.getModel()==null) { + viewModel.process(Action.add(newModel)); + } + else { + viewModel.process(Action.edit(newModel)); + } + + ((Contract)getActivity()).finishEdit(false); + } + + private void delete() { + viewModel.process(Action.delete(binding.getModel())); + ((Contract)getActivity()).finishEdit(true); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/FilterMode.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/FilterMode.java new file mode 100644 index 0000000..030bc38 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/FilterMode.java @@ -0,0 +1,7 @@ +package com.commonsware.todo; + +public enum FilterMode { + ALL, + COMPLETED, + OUTSTANDING +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java new file mode 100644 index 0000000..9379863 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/MainActivity.java @@ -0,0 +1,84 @@ +package com.commonsware.todo; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.inputmethod.InputMethodManager; + +public class MainActivity extends FragmentActivity + implements RosterListFragment.Contract, DisplayFragment.Contract, + EditFragment.Contract { + private static final String BACK_STACK_SHOW="showModel"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getSupportFragmentManager().findFragmentById(android.R.id.content)==null) { + getSupportFragmentManager().beginTransaction() + .add(android.R.id.content, new RosterListFragment()) + .commit(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.actions, menu); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==R.id.about) { + startActivity(new Intent(this, AboutActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void showModel(ToDoModel model) { + getSupportFragmentManager().beginTransaction() + .replace(android.R.id.content, DisplayFragment.newInstance(model)) + .addToBackStack(BACK_STACK_SHOW) + .commit(); + } + + @Override + public void editModel(ToDoModel model) { + getSupportFragmentManager().beginTransaction() + .replace(android.R.id.content, EditFragment.newInstance(model)) + .addToBackStack(null) + .commit(); + } + + @Override + public void addModel() { + editModel(null); + } + + @Override + public void finishEdit(boolean deleted) { + hideSoftInput(); + + if (deleted) { + getSupportFragmentManager().popBackStack(BACK_STACK_SHOW, + FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + else { + getSupportFragmentManager().popBackStack(); + } + } + + private void hideSoftInput() { + if (getCurrentFocus()!=null && getCurrentFocus().getWindowToken()!=null) { + ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); + } + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Result.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Result.java new file mode 100644 index 0000000..0fd8779 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/Result.java @@ -0,0 +1,61 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; +import java.util.Collections; +import java.util.List; + +public abstract class Result { + public static Result added(ToDoModel model) { + return new AutoValue_Result_Added(model); + } + + public static Result modified(ToDoModel model) { + return new AutoValue_Result_Modified(model); + } + + static Result deleted(ToDoModel model) { + return new AutoValue_Result_Deleted(model); + } + + static Result showed(ToDoModel current) { + return new AutoValue_Result_Showed(current); + } + + static Result loaded(List models) { + return new AutoValue_Result_Loaded(Collections.unmodifiableList(models)); + } + + static Result filtered(FilterMode mode) { + return(new AutoValue_Result_Filtered(mode)); + } + + @AutoValue + public static abstract class Added extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Modified extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + public static abstract class Deleted extends Result { + public abstract ToDoModel model(); + } + + @AutoValue + static abstract class Showed extends Result { + public abstract ToDoModel current(); + } + + @AutoValue + public static abstract class Loaded extends Result { + public abstract List models(); + } + + @AutoValue + static abstract class Filtered extends Result { + public abstract FilterMode filterMode(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java new file mode 100644 index 0000000..3db4507 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListAdapter.java @@ -0,0 +1,46 @@ +package com.commonsware.todo; + +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; +import com.commonsware.todo.databinding.TodoRowBinding; +import java.util.List; + +public class RosterListAdapter extends RecyclerView.Adapter { + private List models; + final private RosterListFragment host; + + RosterListAdapter(RosterListFragment host) { + this.host=host; + } + + @Override + public RosterRowHolder onCreateViewHolder(ViewGroup parent, int viewType) { + TodoRowBinding binding= + TodoRowBinding.inflate(host.getLayoutInflater(), parent, false); + + return new RosterRowHolder(binding, this); + } + + @Override + public void onBindViewHolder(RosterRowHolder holder, int position) { + holder.bind(models.get(position)); + } + + @Override + public int getItemCount() { + return models==null ? 0 : models.size(); + } + + public void replace(ToDoModel model, boolean isChecked) { + host.replace(model.toBuilder().isCompleted(isChecked).build()); + } + + void showModel(ToDoModel model) { + host.showModel(model); + } + + void setState(ViewState state) { + models=state.filteredItems(); + notifyDataSetChanged(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java new file mode 100644 index 0000000..35d4a5b --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterListFragment.java @@ -0,0 +1,131 @@ +package com.commonsware.todo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class RosterListFragment extends Fragment { + interface Contract { + void showModel(ToDoModel model); + void addModel(); + } + + private RecyclerView rv; + private View progress; + private TextView empty; + private RosterViewModel viewModel; + private RosterListAdapter adapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + viewModel=ViewModelProviders.of(getActivity()).get(RosterViewModel.class); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_roster, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.add: + ((Contract)getActivity()).addModel(); + return true; + + case R.id.all: + item.setChecked(true); + viewModel.process(Action.filter(FilterMode.ALL)); + return true; + + case R.id.completed: + item.setChecked(true); + viewModel.process(Action.filter(FilterMode.COMPLETED)); + return true; + + case R.id.outstanding: + item.setChecked(true); + viewModel.process(Action.filter(FilterMode.OUTSTANDING)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View result=inflater.inflate(R.layout.todo_roster, container, false); + + rv=result.findViewById(R.id.items); + empty=result.findViewById(R.id.empty); + progress=result.findViewById(R.id.progressBar); + + return result; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + rv.setLayoutManager(new LinearLayoutManager(getActivity())); + + DividerItemDecoration decoration=new DividerItemDecoration(getActivity(), + LinearLayoutManager.VERTICAL); + + rv.addItemDecoration(decoration); + adapter=new RosterListAdapter(this); + rv.setAdapter(adapter); + viewModel.stateStream().observe(this, this::render); + } + + public void render(ViewState state) { + adapter.setState(state); + + if (state.isLoaded()) { + progress.setVisibility(View.GONE); + + if (state.items().size()==0) { + empty.setVisibility(View.VISIBLE); + empty.setText(R.string.msg_empty); + } + else if (state.filteredItems().size()==0) { + empty.setVisibility(View.VISIBLE); + empty.setText(R.string.msg_empty_filtered); + } + else { + empty.setVisibility(View.GONE); + } + } + else { + progress.setVisibility(View.VISIBLE); + empty.setVisibility(View.GONE); + } + } + + void replace(ToDoModel model) { + viewModel.process(Action.edit(model)); + } + + void showModel(ToDoModel model) { + ((RosterListFragment.Contract)getActivity()).showModel(model); + viewModel.process(Action.show(model)); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java new file mode 100644 index 0000000..fc23476 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterRowHolder.java @@ -0,0 +1,32 @@ +package com.commonsware.todo; + +import android.support.v7.widget.RecyclerView; +import com.commonsware.todo.databinding.TodoRowBinding; + +public class RosterRowHolder extends RecyclerView.ViewHolder { + final private TodoRowBinding binding; + private final RosterListAdapter adapter; + + public RosterRowHolder(TodoRowBinding binding, RosterListAdapter adapter) { + super(binding.getRoot()); + + this.binding=binding; + this.adapter=adapter; + } + + void bind(ToDoModel model) { + binding.setModel(model); + binding.setHolder(this); + binding.executePendingBindings(); + } + + public void completeChanged(ToDoModel model, boolean isChecked) { + if (model.isCompleted()!=isChecked) { + adapter.replace(model, isChecked); + } + } + + public void onClick() { + adapter.showModel(binding.getModel()); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java new file mode 100644 index 0000000..920c844 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/RosterViewModel.java @@ -0,0 +1,77 @@ +package com.commonsware.todo; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.LiveDataReactiveStreams; +import android.support.annotation.NonNull; +import java.util.List; +import io.reactivex.BackpressureStrategy; +import io.reactivex.functions.Consumer; +import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.ReplaySubject; + +public class RosterViewModel extends AndroidViewModel { + private LiveData states; + private ViewState lastState=ViewState.empty().build(); + private final PublishSubject actionSubject= + PublishSubject.create(); + private final ReplaySubject stateSubject=ReplaySubject.createWithSize(1); + + public RosterViewModel(@NonNull Application application) { + super(application); + + Controller controller=new Controller(application); + + controller.resultStream() + .subscribe(result -> { + lastState=foldResultIntoState(lastState, result); + stateSubject.onNext(lastState); + }, stateSubject::onError); + + states=LiveDataReactiveStreams + .fromPublisher(stateSubject.toFlowable(BackpressureStrategy.LATEST)); + + controller.subscribeToActions(actionSubject); + process(Action.load()); + } + + public LiveData stateStream() { + return states; + } + + public void process(Action action) { + actionSubject.onNext(action); + } + + private ViewState foldResultIntoState(@NonNull ViewState state, + @NonNull Result result) throws Exception { + if (result instanceof Result.Added) { + return state.add(((Result.Added)result).model()); + } + else if (result instanceof Result.Modified) { + return state.modify(((Result.Modified)result).model()); + } + else if (result instanceof Result.Deleted) { + return state.delete(((Result.Deleted)result).model()); + } + else if (result instanceof Result.Loaded) { + List models=((Result.Loaded)result).models(); + + return ViewState.builder() + .items(models) + .current(models.size()==0 ? null : models.get(0)) + .isLoaded(true) + .build(); + } + else if (result instanceof Result.Showed) { + return state.show(((Result.Showed)result).current()); + } + else if (result instanceof Result.Filtered) { + return state.filter(((Result.Filtered)result).filterMode()); + } + else { + throw new IllegalStateException("Unexpected result type: "+result.toString()); + } + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java new file mode 100644 index 0000000..e238840 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoApp.java @@ -0,0 +1,30 @@ +package com.commonsware.todo; + +import android.app.Application; +import android.os.Build; +import android.os.Handler; +import android.os.StrictMode; + +public class ToDoApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + new Handler().postAtFrontOfQueue(this::enableStrictMode); + } + + private void enableStrictMode() { + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + } + else { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java new file mode 100644 index 0000000..2e11288 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoDatabase.java @@ -0,0 +1,32 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.Room; +import android.arch.persistence.room.RoomDatabase; +import android.arch.persistence.room.TypeConverters; +import android.content.Context; + +@Database(entities={ToDoEntity.class}, version=1) +@TypeConverters({TypeTransmogrifier.class}) +public abstract class ToDoDatabase extends RoomDatabase { + public abstract ToDoEntity.Store todoStore(); + + private static final String DB_NAME="stuff.db"; + private static volatile ToDoDatabase INSTANCE=null; + + synchronized static ToDoDatabase get(Context ctxt) { + if (INSTANCE==null) { + INSTANCE=create(ctxt); + } + + return INSTANCE; + } + + private static ToDoDatabase create(Context ctxt) { + RoomDatabase.Builder b= + Room.databaseBuilder(ctxt.getApplicationContext(), ToDoDatabase.class, + DB_NAME); + + return b.build(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java new file mode 100644 index 0000000..abad5af --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoEntity.java @@ -0,0 +1,64 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Index; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.PrimaryKey; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.support.annotation.NonNull; +import java.util.Calendar; +import java.util.List; + +@Entity(tableName="todos", indices=@Index(value="id")) +public class ToDoEntity { + @PrimaryKey @NonNull final String id; + @NonNull final String description; + final String notes; + final boolean isCompleted; + @NonNull final Calendar createdOn; + + public static ToDoEntity fromModel(ToDoModel model) { + return new ToDoEntity(model.id(), model.description(), model.isCompleted(), + model.notes(), model.createdOn()); + } + + ToDoEntity(@NonNull String id, @NonNull String description, boolean isCompleted, + String notes, @NonNull Calendar createdOn) { + this.id=id; + this.description=description; + this.isCompleted=isCompleted; + this.notes=notes; + this.createdOn=createdOn; + } + + public ToDoModel toModel() { + return ToDoModel.builder() + .id(id) + .description(description) + .isCompleted(isCompleted) + .notes(notes) + .createdOn(createdOn) + .build(); + } + + @Dao + public interface Store { + @Query("SELECT * FROM todos ORDER BY description ASC") + List all(); + + @Insert + void insert(ToDoEntity... entities); + + @Update + void update(ToDoEntity... entities); + + @Delete + void delete(ToDoEntity... entities); + + @Query("DELETE FROM todos") + void deleteAll(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java new file mode 100644 index 0000000..4c0ede8 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoModel.java @@ -0,0 +1,81 @@ +package com.commonsware.todo; + +import com.google.auto.value.AutoValue; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import android.support.annotation.Nullable; + +@AutoValue +public abstract class ToDoModel { + public abstract String id(); + public abstract boolean isCompleted(); + public abstract String description(); + @Nullable public abstract String notes(); + public abstract Calendar createdOn(); + + static Builder builder() { + return new AutoValue_ToDoModel.Builder(); + } + + public static Builder creator() { + return builder() + .isCompleted(false) + .id(UUID.randomUUID().toString()) + .createdOn(Calendar.getInstance()); + } + + public Builder toBuilder() { + return builder() + .id(id()) + .isCompleted(isCompleted()) + .description(description()) + .notes(notes()) + .createdOn(createdOn()); + } + + static final Comparator SORT_BY_DESC= + (one, two) -> (one.description().compareTo(two.description())); + + public static List filter(List models, + FilterMode filterMode) { + List result; + + if (filterMode==FilterMode.COMPLETED) { + result=new ArrayList<>(); + + for (ToDoModel model : models) { + if (model.isCompleted()) { + result.add(model); + } + } + } + else if (filterMode==FilterMode.OUTSTANDING) { + result=new ArrayList<>(); + + for (ToDoModel model : models) { + if (!model.isCompleted()) { + result.add(model); + } + } + } + else { + result=new ArrayList<>(models); + } + + return Collections.unmodifiableList(result); + } + + @AutoValue.Builder + public static abstract class Builder { + abstract Builder id(String id); + public abstract Builder isCompleted(boolean isCompleted); + public abstract Builder description(String desc); + public abstract Builder notes(String notes); + abstract Builder createdOn(Calendar date); + public abstract ToDoModel build(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java new file mode 100644 index 0000000..27870b5 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ToDoRepository.java @@ -0,0 +1,45 @@ +package com.commonsware.todo; + +import android.content.Context; +import java.util.ArrayList; +import java.util.List; + +public class ToDoRepository { + private static volatile ToDoRepository INSTANCE=null; + private final ToDoDatabase db; + + public synchronized static ToDoRepository get(Context ctxt) { + if (INSTANCE==null) { + INSTANCE=new ToDoRepository(ctxt.getApplicationContext()); + } + + return INSTANCE; + } + + private ToDoRepository(Context ctxt) { + db=ToDoDatabase.get(ctxt); + } + + public List all() { + List entities=db.todoStore().all(); + ArrayList result=new ArrayList<>(entities.size()); + + for (ToDoEntity entity : entities) { + result.add(entity.toModel()); + } + + return result; + } + + public void add(ToDoModel model) { + db.todoStore().insert(ToDoEntity.fromModel(model)); + } + + public void replace(ToDoModel model) { + db.todoStore().update(ToDoEntity.fromModel(model)); + } + + public void delete(ToDoModel model) { + db.todoStore().delete(ToDoEntity.fromModel(model)); + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java new file mode 100644 index 0000000..b2a2ec2 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/TypeTransmogrifier.java @@ -0,0 +1,28 @@ +package com.commonsware.todo; + +import android.arch.persistence.room.TypeConverter; +import java.util.Calendar; + +public class TypeTransmogrifier { + @TypeConverter + public static Long fromCalendar(Calendar date) { + if (date==null) { + return null; + } + + return date.getTimeInMillis(); + } + + @TypeConverter + public static Calendar toCalendar(Long millisSinceEpoch) { + if (millisSinceEpoch==null) { + return null; + } + + Calendar result=Calendar.getInstance(); + + result.setTimeInMillis(millisSinceEpoch); + + return result; + } +} diff --git a/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java new file mode 100644 index 0000000..d508e3e --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/java/com/commonsware/todo/ViewState.java @@ -0,0 +1,130 @@ +package com.commonsware.todo; + +import android.support.annotation.Nullable; +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@AutoValue +public abstract class ViewState { + public abstract List items(); + @Nullable public abstract ToDoModel current(); + public abstract boolean isLoaded(); + public abstract FilterMode filterMode(); + + static Builder builder() { + return new AutoValue_ViewState.Builder() + .isLoaded(false) + .filterMode(FilterMode.ALL); + } + + static Builder empty() { + return builder().items(Collections.unmodifiableList(new ArrayList<>())); + } + + Builder toBuilder() { + return builder() + .items(items()) + .current(current()) + .isLoaded(isLoaded()) + .filterMode(filterMode()); + } + + ViewState add(ToDoModel model) { + List models=new ArrayList<>(items()); + + models.add(model); + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(model) + .build(); + } + + ViewState modify(ToDoModel model) { + List models=new ArrayList<>(items()); + ToDoModel original=find(models, model.id()); + + if (original!=null) { + int index=models.indexOf(original); + models.set(index, model); + } + + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(model) + .build(); + } + + ViewState delete(ToDoModel model) { + List models=new ArrayList<>(items()); + ToDoModel original=find(models, model.id()); + + if (original==null) { + throw new IllegalArgumentException("Cannot find model to delete: "+model.toString()); + } + else { + models.remove(original); + } + + sort(models); + + return toBuilder() + .items(Collections.unmodifiableList(models)) + .current(null) + .build(); + } + + ViewState show(ToDoModel current) { + return toBuilder() + .current(current) + .build(); + } + + ViewState filter(FilterMode mode) { + return(toBuilder() + .filterMode(mode) + .build()); + } + + private ToDoModel find(List models, String id) { + int position=findPosition(models, id); + + return position>=0 ? models.get(position) : null; + } + + private int findPosition(List models, String id) { + for (int i=0;i models) { + Collections.sort(models, ToDoModel.SORT_BY_DESC); + } + + @Memoized + public List filteredItems() { + return(ToDoModel.filter(items(), filterMode())); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder items(List items); + abstract Builder current(ToDoModel current); + abstract Builder isLoaded(boolean isLoaded); + abstract Builder filterMode(FilterMode filterMode); + abstract ViewState build(); + } +} diff --git a/T32-Filter/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/T32-Filter/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..dfe5c75 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 0000000..0258249 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml new file mode 100644 index 0000000..1241eda --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_check_circle_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml new file mode 100644 index 0000000..39e64d6 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_delete_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml new file mode 100644 index 0000000..2ab2fb7 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_edit_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_filter_list_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_filter_list_black_24dp.xml new file mode 100644 index 0000000..b99b672 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_filter_list_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml new file mode 100644 index 0000000..cf53e14 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_info_outline_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_launcher_background.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..c975946 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/T32-Filter/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml b/T32-Filter/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml new file mode 100644 index 0000000..a561d63 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/drawable/ic_save_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/T32-Filter/ToDo/app/src/main/res/layout/activity_about.xml b/T32-Filter/ToDo/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..f86cabf --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/T32-Filter/ToDo/app/src/main/res/layout/todo_display.xml b/T32-Filter/ToDo/app/src/main/res/layout/todo_display.xml new file mode 100644 index 0000000..10f771f --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/layout/todo_display.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/layout/todo_edit.xml b/T32-Filter/ToDo/app/src/main/res/layout/todo_edit.xml new file mode 100644 index 0000000..98ba52e --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/layout/todo_edit.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/layout/todo_roster.xml b/T32-Filter/ToDo/app/src/main/res/layout/todo_roster.xml new file mode 100644 index 0000000..e1b05c5 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/layout/todo_roster.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/T32-Filter/ToDo/app/src/main/res/layout/todo_row.xml b/T32-Filter/ToDo/app/src/main/res/layout/todo_row.xml new file mode 100644 index 0000000..09e37e2 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/layout/todo_row.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + diff --git a/T32-Filter/ToDo/app/src/main/res/menu/actions.xml b/T32-Filter/ToDo/app/src/main/res/menu/actions.xml new file mode 100644 index 0000000..791605e --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/menu/actions.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/menu/actions_display.xml b/T32-Filter/ToDo/app/src/main/res/menu/actions_display.xml new file mode 100644 index 0000000..b1ae1a4 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/menu/actions_display.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/menu/actions_edit.xml b/T32-Filter/ToDo/app/src/main/res/menu/actions_edit.xml new file mode 100644 index 0000000..6e53ed7 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/menu/actions_edit.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/menu/actions_roster.xml b/T32-Filter/ToDo/app/src/main/res/menu/actions_roster.xml new file mode 100644 index 0000000..b7387f7 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/menu/actions_roster.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..6f53cf8 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ca82f3a Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..6f53cf8 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..20094c0 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1295659 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..20094c0 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..296555b Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a436a78 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..296555b Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..904eb35 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ac1562e Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..904eb35 Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b1e8d5a Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8ce4e1b Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b1e8d5a Binary files /dev/null and b/T32-Filter/ToDo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/T32-Filter/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml b/T32-Filter/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml new file mode 100644 index 0000000..2780f07 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/values/colors_yellow_light_blue.xml @@ -0,0 +1,11 @@ + + + #FFEB3B + #FBC02D + #FFF9C4 + #03a9f4 + #212121 + #757575 + #212121 + #BDBDBD + diff --git a/T32-Filter/ToDo/app/src/main/res/values/dimens.xml b/T32-Filter/ToDo/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..0b352e4 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/values/dimens.xml @@ -0,0 +1,9 @@ + + + 16sp + 48dp + 24sp + 16sp + 20sp + 8dp + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/values/ic_launcher_background.xml b/T32-Filter/ToDo/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..092305f --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F4D112 + \ No newline at end of file diff --git a/T32-Filter/ToDo/app/src/main/res/values/strings.xml b/T32-Filter/ToDo/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..e1bca5f --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + ToDo + Click the + icon to add a todo item! + Click the + icon to add a todo item, or change your filter to show other items + About + Item is completed + Created on: + Edit + Description + Notes + Save + Add + Delete + Filter + All + Completed + Outstanding + diff --git a/T32-Filter/ToDo/app/src/main/res/values/styles.xml b/T32-Filter/ToDo/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..32b7ff8 --- /dev/null +++ b/T32-Filter/ToDo/app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/T32-Filter/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java b/T32-Filter/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java new file mode 100644 index 0000000..f4b5e4c --- /dev/null +++ b/T32-Filter/ToDo/app/src/test/java/com/commonsware/todo/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.commonsware.todo; + +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() throws Exception { + assertEquals(4, 2+2); + } +} \ No newline at end of file diff --git a/T32-Filter/ToDo/build.gradle b/T32-Filter/ToDo/build.gradle new file mode 100644 index 0000000..1a3d812 --- /dev/null +++ b/T32-Filter/ToDo/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.2' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/T32-Filter/ToDo/gradle.properties b/T32-Filter/ToDo/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/T32-Filter/ToDo/gradle.properties @@ -0,0 +1,17 @@ +# 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. +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 diff --git a/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.jar b/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.properties b/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..770a728 --- /dev/null +++ b/T32-Filter/ToDo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Nov 14 13:31:09 EST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/T32-Filter/ToDo/gradlew b/T32-Filter/ToDo/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/T32-Filter/ToDo/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/T32-Filter/ToDo/gradlew.bat b/T32-Filter/ToDo/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/T32-Filter/ToDo/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/T32-Filter/ToDo/settings.gradle b/T32-Filter/ToDo/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/T32-Filter/ToDo/settings.gradle @@ -0,0 +1 @@ +include ':app'