├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_save.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_save.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_save.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── ic_save.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_add_note.xml │ │ │ │ ├── ic_delete.xml │ │ │ │ ├── ic_add_a_photo_black_24dp.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── menu │ │ │ │ └── menu_save.xml │ │ │ ├── layout │ │ │ │ ├── layout_new_note_adapter.xml │ │ │ │ ├── activity_single_note_selection.xml │ │ │ │ ├── card_notes.xml │ │ │ │ ├── activity_display_note.xml │ │ │ │ └── activity_new_note.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── lingaraj │ │ │ │ └── hourglass │ │ │ │ └── in │ │ │ │ └── hlanechallenge │ │ │ │ ├── database │ │ │ │ ├── HLaneDatabase.java │ │ │ │ └── NotesTable.java │ │ │ │ ├── HouseLaneApp.java │ │ │ │ ├── model │ │ │ │ ├── Note.java │ │ │ │ └── NotesData.java │ │ │ │ ├── adapters │ │ │ │ ├── NewNoteImageAdapter.java │ │ │ │ └── DisplayNotesAdapter.java │ │ │ │ ├── NoteImageView.java │ │ │ │ ├── activities │ │ │ │ ├── NoteFullViewActivity.java │ │ │ │ ├── DisplayNotesActivity.java │ │ │ │ └── NewNoteActivity.java │ │ │ │ └── Helpers.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── lingaraj │ │ │ └── hourglass │ │ │ └── in │ │ │ └── hlanechallenge │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── lingaraj │ │ └── hourglass │ │ └── in │ │ └── hlanechallenge │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/drawable-hdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/drawable-mdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/drawable-xhdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/drawable-xxhdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/HLaneChallenge/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4dp 4 | 4dp 5 | 16dp 6 | 100dp 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 15 22:39:56 IST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_note.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_save.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/database/HLaneDatabase.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.database; 2 | 3 | import com.raizlabs.android.dbflow.annotation.Database; 4 | 5 | /** 6 | * Created by lingaraj on 1/16/18. 7 | */ 8 | @Database(name = HLaneDatabase.NAME, version = HLaneDatabase.VERSION) 9 | public class HLaneDatabase { 10 | 11 | public static final String NAME = "hlane_database"; 12 | public static final int VERSION = 4; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/test/java/lingaraj/hourglass/in/hlanechallenge/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test public void addition_isCorrect() throws Exception { 14 | assertEquals(4, 2 + 2); 15 | } 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HLaneChallenge 2 | Enable storage permission from App info for device running over marshmallow and above. 3 | 4 | ### Screen One 5 | ![display_notes](https://user-images.githubusercontent.com/8025146/35078055-57c7bb1e-fc26-11e7-9039-cc7797f810b3.png) 6 | 7 | ### Screen Two 8 | ![Create New Note](https://user-images.githubusercontent.com/8025146/35078058-58204fc2-fc26-11e7-956b-4c7e3c45f9b1.png) 9 | 10 | ### Screen Three 11 | ![Display a selected Note](https://user-images.githubusercontent.com/8025146/35078056-57f3a3dc-fc26-11e7-9278-e55da9d39cb5.png) 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_new_note_adapter.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00A8E1 4 | #007CBA 5 | #00A8E1 6 | #FFFFFF 7 | #616161 8 | #9E9E9E 9 | #F44336 10 | #616161 11 | #757575 12 | #757575 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_a_photo_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | HLaneChallenge 3 | Title 4 | Enter Content 5 | Save 6 | Either Title or Content is empty, Note not saved 7 | Note Saved 8 | Loading 9 | Note 10 | Created At 11 | Notes 12 | No Notes Available. Like to create one? 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/HouseLaneApp.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge; 2 | 3 | import android.app.Application; 4 | import com.raizlabs.android.dbflow.config.FlowManager; 5 | import lingaraj.hourglass.in.hlanechallenge.model.NotesData; 6 | 7 | /** 8 | * Created by lingaraj on 1/16/18. 9 | */ 10 | 11 | public class HouseLaneApp extends Application 12 | { 13 | 14 | private NotesData notesData; 15 | 16 | public NotesData getNotesData() { 17 | return notesData; 18 | } 19 | 20 | public void setNotesData(NotesData notesData) { 21 | this.notesData = notesData; 22 | } 23 | 24 | private final String TAG = "HOUSELANEAPP"; 25 | @Override public void onCreate() { 26 | super.onCreate(); 27 | FlowManager.init(this); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/lingaraj/hourglass/in/hlanechallenge/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { 18 | @Test public void useAppContext() throws Exception { 19 | // Context of the app under test. 20 | Context appContext = InstrumentationRegistry.getTargetContext(); 21 | 22 | assertEquals("lingaraj.hourglass.in.hlanechallenge", appContext.getPackageName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/model/Note.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by lingaraj on 1/16/18. 7 | */ 8 | 9 | public class Note { 10 | private long id; 11 | private String title; 12 | private String text; 13 | private List images; 14 | private String createdAt; 15 | 16 | public Note(long record_id, String title, String text, String created_at, List images) { 17 | this.id = record_id; 18 | this.title = title; 19 | this.text = text; 20 | this.createdAt = created_at; 21 | this.images = images; 22 | } 23 | 24 | 25 | public long getId() { 26 | return id; 27 | } 28 | 29 | public void setId(long id) { 30 | this.id = id; 31 | } 32 | 33 | public String getTitle() { 34 | return title; 35 | } 36 | 37 | public void setTitle(String title) { 38 | this.title = title; 39 | } 40 | 41 | 42 | public List getImages() { 43 | return images; 44 | } 45 | 46 | public void setImages(List images) { 47 | this.images = images; 48 | } 49 | 50 | public String getCreatedAt() { 51 | return createdAt; 52 | } 53 | 54 | public void setCreatedAt(String createdAt) { 55 | this.createdAt = createdAt; 56 | } 57 | 58 | public String getText() { 59 | return text; 60 | } 61 | 62 | public void setText(String text) { 63 | this.text = text; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/database/NotesTable.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.database; 2 | 3 | import com.raizlabs.android.dbflow.annotation.Column; 4 | import com.raizlabs.android.dbflow.annotation.PrimaryKey; 5 | import com.raizlabs.android.dbflow.annotation.Table; 6 | import com.raizlabs.android.dbflow.structure.BaseModel; 7 | 8 | /** 9 | * Created by lingaraj on 1/16/18. 10 | */ 11 | @Table(databaseName = HLaneDatabase.NAME, tableName = NotesTable.NAME) 12 | public class NotesTable extends BaseModel { 13 | 14 | public static final String NAME = "note_table"; 15 | 16 | @PrimaryKey(autoincrement = true) 17 | @Column 18 | private long id; 19 | 20 | @Column 21 | private String title; 22 | 23 | @Column 24 | private String text; 25 | 26 | @Column 27 | private String images; 28 | 29 | @Column 30 | private String createdAt; 31 | 32 | public long getId() { 33 | return id; 34 | } 35 | 36 | public void setId(long id) { 37 | this.id = id; 38 | } 39 | 40 | public String getTitle() { 41 | return title; 42 | } 43 | 44 | public void setTitle(String title) { 45 | this.title = title; 46 | } 47 | 48 | public String getText() { 49 | return text; 50 | } 51 | 52 | public void setText(String text) { 53 | this.text = text; 54 | } 55 | 56 | public String getImages() { 57 | return images; 58 | } 59 | 60 | public void setImages(String images) { 61 | this.images = images; 62 | } 63 | 64 | public String getCreatedAt() { 65 | return createdAt; 66 | } 67 | 68 | public void setCreatedAt(String createdAt) { 69 | this.createdAt = createdAt; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | dataBinding.enabled = true; 5 | compileSdkVersion 27 6 | buildToolsVersion '26.0.2' 7 | 8 | defaultConfig { 9 | applicationId "lingaraj.hourglass.in.hlanechallenge" 10 | minSdkVersion 19 11 | targetSdkVersion 27 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | vectorDrawables.useSupportLibrary = true 16 | 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'com.android.support:appcompat-v7:27.0.2' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | compile 'com.android.support:support-vector-drawable:27.0.2' 34 | compile 'com.android.support:support-annotations:27.0.2' 35 | compile 'com.android.support:recyclerview-v7:27.0.2' 36 | compile 'com.android.support:cardview-v7:27.0.2' 37 | compile 'com.android.support:support-v4:27.0.2' 38 | compile 'com.squareup.picasso:picasso:2.5.2' 39 | annotationProcessor 'com.raizlabs.android:DBFlow-Compiler:2.2.1' 40 | compile 'com.raizlabs.android:DBFlow-Core:2.2.1' 41 | compile 'com.raizlabs.android:DBFlow:2.2.1' 42 | compile 'com.android.support:design:27.0.2' 43 | annotationProcessor 'com.android.databinding:compiler:2.3.0' 44 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 45 | compile 'com.pnikosis:materialish-progress:1.7' 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 

 7 | 8 | 
 9 | 23 | 24 | 25 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/adapters/NewNoteImageAdapter.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.adapters; 2 | 3 | /** 4 | * Created by lingaraj on 1/16/18. 5 | */ 6 | 7 | import android.databinding.DataBindingUtil; 8 | import android.net.Uri; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import lingaraj.hourglass.in.hlanechallenge.R; 16 | import lingaraj.hourglass.in.hlanechallenge.databinding.LayoutNewNoteAdapterBinding; 17 | 18 | /** 19 | * Created by lingaraj on 5/10/16. 20 | */ 21 | 22 | 23 | public class NewNoteImageAdapter extends RecyclerView.Adapter { 24 | 25 | private List images = new ArrayList(); 26 | 27 | public static class ViewHolder extends RecyclerView.ViewHolder { 28 | // each data item is just a string in this case 29 | LayoutNewNoteAdapterBinding binding; 30 | public ViewHolder(View v) { 31 | super(v); 32 | binding = DataBindingUtil.bind(v); 33 | } 34 | } 35 | 36 | public NewNoteImageAdapter() { 37 | //empty constructor for the adapter 38 | } 39 | 40 | @Override 41 | public NewNoteImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 42 | // create a new view 43 | View v = LayoutInflater.from(parent.getContext()) 44 | .inflate(R.layout.layout_new_note_adapter, parent, false); 45 | // set the view's size, margins, paddings and layout parameters 46 | return new ViewHolder(v); 47 | } 48 | 49 | @Override 50 | public void onBindViewHolder(NewNoteImageAdapter.ViewHolder holder, int position) { 51 | String image_path_string = images.get(position); 52 | holder.binding.imageView.setImageURI(Uri.parse(image_path_string)); 53 | } 54 | 55 | public void setNewData(List images) 56 | { 57 | this.images = images; 58 | notifyDataSetChanged(); 59 | } 60 | 61 | 62 | @Override 63 | public int getItemCount() { 64 | return this.images.size(); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/NoteImageView.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.ColorMatrix; 8 | import android.graphics.ColorMatrixColorFilter; 9 | import android.graphics.Matrix; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.PorterDuffColorFilter; 12 | import android.graphics.drawable.Drawable; 13 | import android.net.Uri; 14 | import android.support.annotation.Nullable; 15 | import android.util.AttributeSet; 16 | import android.util.Log; 17 | import java.util.Locale; 18 | 19 | /** 20 | * Created by lingaraj on 1/16/18. 21 | */ 22 | 23 | public class NoteImageView extends android.support.v7.widget.AppCompatImageView 24 | { 25 | private final String TAG = "NOTEIMGVIEW"; 26 | 27 | public NoteImageView(Context context) { 28 | super(context); 29 | } 30 | 31 | public NoteImageView(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | } 34 | 35 | public NoteImageView(Context context, AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | } 38 | 39 | @Override 40 | public void setScaleType(ScaleType scaleType) { 41 | super.setScaleType(scaleType); 42 | } 43 | 44 | @Override 45 | public void setImageURI(@Nullable Uri uri) { 46 | super.setImageURI(uri); 47 | } 48 | 49 | @Override 50 | public void setImageMatrix(Matrix matrix) { 51 | super.setImageMatrix(matrix); 52 | } 53 | 54 | @Override public void setColorFilter(ColorFilter cf) { 55 | super.setColorFilter(cf); 56 | } 57 | 58 | 59 | public void resetColorFilter(){ 60 | //if the custom imageview was set with grey color this will reset the filter to its original state 61 | this.setColorFilter(null); 62 | this.setImageAlpha(255); 63 | this.invalidate(); 64 | Log.d(TAG,"Grey Color Filter removed"); 65 | } 66 | 67 | public void setGreyScaleFilter(){ 68 | //sets the grey color filter to the Imageview 69 | ColorMatrix matrix = new ColorMatrix(); 70 | matrix.setSaturation(0); //0 means grayscale 71 | ColorMatrixColorFilter cf = new ColorMatrixColorFilter(matrix); 72 | this.setColorFilter(cf); 73 | this.setImageAlpha(128); // 128 = 0.5 74 | this.invalidate(); 75 | Log.d(TAG,"Grey Color Filter set"); 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_single_note_selection.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 12 | 13 | 21 | 25 | 35 | 36 | 47 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_notes.xml: -------------------------------------------------------------------------------- 1 | 3 | 11 | 15 | 20 | 30 | 31 | 42 | 46 | 56 | 57 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/activities/NoteFullViewActivity.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.activities; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.util.Log; 9 | import android.view.View; 10 | import lingaraj.hourglass.in.hlanechallenge.HouseLaneApp; 11 | import lingaraj.hourglass.in.hlanechallenge.R; 12 | import lingaraj.hourglass.in.hlanechallenge.adapters.NewNoteImageAdapter; 13 | import lingaraj.hourglass.in.hlanechallenge.databinding.ActivitySingleNoteSelectionBinding; 14 | import lingaraj.hourglass.in.hlanechallenge.model.Note; 15 | import lingaraj.hourglass.in.hlanechallenge.model.NotesData; 16 | 17 | /** 18 | * Created by lingaraj on 1/18/18. 19 | */ 20 | 21 | public class NoteFullViewActivity extends AppCompatActivity { 22 | 23 | private final String TAG = "NOTEFULLVIEW"; 24 | private HouseLaneApp app; 25 | private NotesData notes_data; 26 | private ActivitySingleNoteSelectionBinding activity_binding; 27 | private NewNoteImageAdapter mAdapter; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_single_note_selection); 33 | activity_binding = DataBindingUtil.setContentView(NoteFullViewActivity.this,R.layout.activity_single_note_selection); 34 | app = (HouseLaneApp) getApplication(); 35 | notes_data = app.getNotesData(); 36 | Long record_id = getIntent().getLongExtra("record_id",0); 37 | Note note = this.notes_data.getNotesMap().get(record_id); 38 | setData(note); 39 | } 40 | 41 | private void setData(Note record) { 42 | activity_binding.title.setText(record.getTitle()); 43 | activity_binding.content.setText(record.getText()); 44 | String created_time = "Created At:"+" "+record.getCreatedAt(); 45 | activity_binding.createdAt.setText(created_time); 46 | if (record.getImages().size()>0){ 47 | //contains images use adapter to display data 48 | mAdapter = new NewNoteImageAdapter(); 49 | activity_binding.imagesRecyclerview.setNestedScrollingEnabled(true); 50 | activity_binding.imagesRecyclerview.setHasFixedSize(true); 51 | activity_binding.imagesRecyclerview.setLayoutManager(new LinearLayoutManager(getApplicationContext(),LinearLayoutManager.VERTICAL,false)); 52 | activity_binding.imagesRecyclerview.setAdapter(mAdapter); 53 | mAdapter.setNewData(record.getImages()); 54 | activity_binding.imagesRecyclerview.setVisibility(View.VISIBLE); 55 | } 56 | else { 57 | activity_binding.imagesRecyclerview.setVisibility(View.GONE); 58 | } 59 | Log.d(TAG,"Data set on activity"); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_display_note.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | 13 | 14 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 39 | 40 | 47 | 48 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_new_note.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | 14 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 40 | 41 | 46 | 47 | 54 | 55 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/model/NotesData.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.model; 2 | 3 | import android.util.Log; 4 | import com.google.gson.Gson; 5 | import com.google.gson.reflect.TypeToken; 6 | import com.raizlabs.android.dbflow.sql.language.Select; 7 | import java.lang.reflect.Type; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import lingaraj.hourglass.in.hlanechallenge.database.NotesTable; 13 | 14 | /** 15 | * Created by lingaraj on 1/16/18. 16 | */ 17 | 18 | public class NotesData { 19 | private final String TAG = "NOTESDATA"; 20 | private Map notesMap = new HashMap<>(); 21 | 22 | public Map getNotesMap() { 23 | return notesMap; 24 | } 25 | 26 | 27 | public NotesData(){ 28 | fetchOfflineNotes(); 29 | } 30 | 31 | private void fetchOfflineNotes() { 32 | //fetch notes which were saved in db and populate it to views once came back. 33 | long count = new Select().count().from(NotesTable.class).count(); 34 | if (count>0){ 35 | List records = new Select().from(NotesTable.class).queryList(); 36 | for (NotesTable record:records) { 37 | Note note = null; 38 | if (record.getImages()!=null && !record.getImages().isEmpty()){ 39 | //if the images string is not empty which mean the current note contain images , convert back to list using gson 40 | Type typeIndicatorForGson = new TypeToken>() {}.getType(); 41 | List images = new Gson().fromJson(record.getImages(),typeIndicatorForGson); 42 | note = new Note(record.getId(),record.getTitle(),record.getText(),record.getCreatedAt(),images); 43 | } 44 | else { 45 | //if the image string is empty 46 | note = new Note(record.getId(),record.getTitle(),record.getText(),record.getCreatedAt(),new ArrayList()); 47 | 48 | } 49 | notesMap.put(record.getId(),note); 50 | } 51 | } 52 | } 53 | 54 | public void addNote(String title,String text,String created_at,List images){ 55 | //add a new note map to both db and notesmap 56 | long record_id = saveToNotesTable(title,text,created_at,images); 57 | Note note = new Note(record_id,title,text,created_at,images); 58 | notesMap.put(record_id,note); 59 | Log.d(TAG,"Note added to notes list"); 60 | } 61 | 62 | public void deleteNote(long note_id){ 63 | // delete a note from both db and notesmap 64 | removeFromDB(note_id); 65 | notesMap.remove(note_id); 66 | Log.d(TAG,"Record Removed from map:"+note_id); 67 | } 68 | 69 | private void removeFromDB(long note_id) { 70 | NotesTable record = new Select().from(NotesTable.class).where("id=?",note_id).querySingle(); 71 | record.delete(); 72 | Log.d(TAG,"Record removed from Db:"+note_id); 73 | } 74 | 75 | private long saveToNotesTable(String title, String text, String created_at, List images) { 76 | //saves Newly created record to Notes Table and return the long id of the record so that it can be used with the list data as well 77 | NotesTable record = new NotesTable(); 78 | if (images.size()>0){ 79 | //an images have been added to the note 80 | record.setImages(new Gson().toJson(images)); 81 | } 82 | record.setCreatedAt(created_at); 83 | record.setTitle(title); 84 | record.setText(text); 85 | record.setCreatedAt(created_at); 86 | record.save(); 87 | Log.d(TAG,"Record added to notes table:"+title); 88 | return record.getId(); 89 | } 90 | 91 | 92 | 93 | 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/adapters/DisplayNotesAdapter.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.adapters; 2 | 3 | /** 4 | * Created by lingaraj on 1/17/18. 5 | */ 6 | 7 | import android.content.Context; 8 | import android.databinding.DataBindingUtil; 9 | import android.net.Uri; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ImageView; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import lingaraj.hourglass.in.hlanechallenge.R; 21 | import lingaraj.hourglass.in.hlanechallenge.activities.DisplayNotesActivity; 22 | import lingaraj.hourglass.in.hlanechallenge.databinding.CardNotesBinding; 23 | import lingaraj.hourglass.in.hlanechallenge.model.Note; 24 | 25 | public class DisplayNotesAdapter extends RecyclerView.Adapter { 26 | 27 | private List images = new ArrayList(); 28 | private NewNoteImageAdapter image_adapter = new NewNoteImageAdapter(); 29 | private List keyset; 30 | private Map notes = new HashMap<>(); 31 | private DisplayNotesActivity.CardClick card_click; 32 | private DisplayNotesActivity.DeleteClick delete_click; 33 | private Context mContext; 34 | 35 | public Long getRecordId(int position) { 36 | return this.keyset.get(position); 37 | } 38 | 39 | public static class ViewHolder extends RecyclerView.ViewHolder { 40 | // each data item is just a string in this case 41 | CardNotesBinding binding; 42 | public ViewHolder(View v) { 43 | super(v); 44 | binding = DataBindingUtil.bind(v); 45 | } 46 | } 47 | 48 | public DisplayNotesAdapter(Context context,DisplayNotesActivity.CardClick cardClick,DisplayNotesActivity.DeleteClick deleteClick) { 49 | //empty constructor for the adapter 50 | this.card_click = cardClick; 51 | this.delete_click = deleteClick; 52 | this.mContext = context; 53 | } 54 | 55 | @Override 56 | public DisplayNotesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 57 | // create a new view 58 | View v = LayoutInflater.from(parent.getContext()) 59 | .inflate(R.layout.card_notes, parent, false); 60 | // set the view's size, margins, paddings and layout parameters 61 | v.setOnClickListener(this.card_click); 62 | ImageView delete = (ImageView) v.findViewById(R.id.delete); 63 | delete.setOnClickListener(this.delete_click); 64 | return new ViewHolder(v); 65 | } 66 | 67 | @Override 68 | public void onBindViewHolder(DisplayNotesAdapter.ViewHolder holder, int position) { 69 | Long key = this.keyset.get(position); 70 | Note record = this.notes.get(key); 71 | holder.binding.title.setText(record.getTitle()); 72 | holder.binding.content.setText(record.getText()); 73 | String creadted_text = "Created At"+" "+record.getCreatedAt(); 74 | holder.binding.createdAt.setText(creadted_text); 75 | handleImages(holder,record.getImages()); 76 | } 77 | 78 | private void handleImages(ViewHolder holder, List images) { 79 | if (images.size()>0){ 80 | NewNoteImageAdapter images_adapter = new NewNoteImageAdapter(); 81 | holder.binding.imagesRecyclerview.setNestedScrollingEnabled(true); 82 | holder.binding.imagesRecyclerview.setHasFixedSize(true); 83 | holder.binding.imagesRecyclerview.setLayoutManager(new LinearLayoutManager(this.mContext,LinearLayoutManager.HORIZONTAL,false)); 84 | holder.binding.imagesRecyclerview.setAdapter(images_adapter); 85 | images_adapter.setNewData(images); 86 | holder.binding.imagesRecyclerview.setVisibility(View.VISIBLE); 87 | } 88 | else { 89 | holder.binding.imagesRecyclerview.setVisibility(View.GONE); 90 | } 91 | 92 | } 93 | 94 | public void setNewData(Map notes_data) 95 | { 96 | this.notes = notes_data; 97 | this.keyset = new ArrayList<>(this.notes.keySet()); 98 | notifyDataSetChanged(); 99 | } 100 | 101 | 102 | @Override 103 | public int getItemCount() { 104 | 105 | return this.notes.size(); 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/Helpers.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.provider.DocumentsContract; 10 | import android.provider.MediaStore; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Calendar; 13 | 14 | /** 15 | * Created by lingaraj on 1/17/18. 16 | */ 17 | 18 | public class Helpers { 19 | 20 | public static String noteCreatedAt(){ 21 | String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()); 22 | return date; 23 | } 24 | public static String getPath(final Context context, final Uri uri) { 25 | 26 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 27 | 28 | // DocumentProvider 29 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 30 | // ExternalStorageProvider 31 | if (isExternalStorageDocument(uri)) { 32 | final String docId = DocumentsContract.getDocumentId(uri); 33 | final String[] split = docId.split(":"); 34 | final String type = split[0]; 35 | 36 | if ("primary".equalsIgnoreCase(type)) { 37 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 38 | } 39 | 40 | // TODO handle non-primary volumes 41 | } 42 | // DownloadsProvider 43 | else if (isDownloadsDocument(uri)) { 44 | 45 | final String id = DocumentsContract.getDocumentId(uri); 46 | final Uri contentUri = ContentUris.withAppendedId( 47 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 48 | 49 | return getDataColumn(context, contentUri, null, null); 50 | } 51 | // MediaProvider 52 | else if (isMediaDocument(uri)) { 53 | final String docId = DocumentsContract.getDocumentId(uri); 54 | final String[] split = docId.split(":"); 55 | final String type = split[0]; 56 | 57 | Uri contentUri = null; 58 | if ("image".equals(type)) { 59 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 60 | } else if ("video".equals(type)) { 61 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 62 | } else if ("audio".equals(type)) { 63 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 64 | } 65 | 66 | final String selection = "_id=?"; 67 | final String[] selectionArgs = new String[] { 68 | split[1] 69 | }; 70 | 71 | return getDataColumn(context, contentUri, selection, selectionArgs); 72 | } 73 | } 74 | // MediaStore (and general) 75 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 76 | return getDataColumn(context, uri, null, null); 77 | } 78 | // File 79 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 80 | return uri.getPath(); 81 | } 82 | 83 | return null; 84 | } 85 | 86 | /** 87 | * Get the value of the data column for this Uri. This is useful for 88 | * MediaStore Uris, and other file-based ContentProviders. 89 | * 90 | * @param context The context. 91 | * @param uri The Uri to query. 92 | * @param selection (Optional) Filter used in the query. 93 | * @param selectionArgs (Optional) Selection arguments used in the query. 94 | * @return The value of the _data column, which is typically a file path. 95 | */ 96 | public static String getDataColumn(Context context, Uri uri, String selection, 97 | String[] selectionArgs) { 98 | 99 | Cursor cursor = null; 100 | final String column = "_data"; 101 | final String[] projection = { 102 | column 103 | }; 104 | 105 | try { 106 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 107 | null); 108 | if (cursor != null && cursor.moveToFirst()) { 109 | final int column_index = cursor.getColumnIndexOrThrow(column); 110 | return cursor.getString(column_index); 111 | } 112 | } finally { 113 | if (cursor != null) 114 | cursor.close(); 115 | } 116 | return null; 117 | } 118 | 119 | 120 | /** 121 | * @param uri The Uri to check. 122 | * @return Whether the Uri authority is ExternalStorageProvider. 123 | */ 124 | public static boolean isExternalStorageDocument(Uri uri) { 125 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 126 | } 127 | 128 | /** 129 | * @param uri The Uri to check. 130 | * @return Whether the Uri authority is DownloadsProvider. 131 | */ 132 | public static boolean isDownloadsDocument(Uri uri) { 133 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 134 | } 135 | 136 | /** 137 | * @param uri The Uri to check. 138 | * @return Whether the Uri authority is MediaProvider. 139 | */ 140 | public static boolean isMediaDocument(Uri uri) { 141 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/activities/DisplayNotesActivity.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.activities; 2 | 3 | import android.content.Intent; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.util.Log; 10 | import android.view.View; 11 | import lingaraj.hourglass.in.hlanechallenge.HouseLaneApp; 12 | import lingaraj.hourglass.in.hlanechallenge.R; 13 | import lingaraj.hourglass.in.hlanechallenge.adapters.DisplayNotesAdapter; 14 | import lingaraj.hourglass.in.hlanechallenge.databinding.ActivityDisplayNoteBinding; 15 | import lingaraj.hourglass.in.hlanechallenge.model.NotesData; 16 | 17 | /** 18 | * Created by lingaraj on 1/17/18. 19 | */ 20 | 21 | public class DisplayNotesActivity extends AppCompatActivity { 22 | private final String TAG = "DISPNOTEACT"; 23 | ActivityDisplayNoteBinding activity_binding; 24 | private HouseLaneApp app; 25 | private NotesData notes_data; 26 | private DisplayNotesAdapter mAdapter; 27 | private int NEW_NOTE_ADD_REQUEST_CODE = 100; 28 | private boolean notify_adapter = false; 29 | 30 | @Override protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_display_note); 33 | activity_binding = DataBindingUtil.setContentView(this, R.layout.activity_display_note); 34 | activity_binding.fabAddNote.setOnClickListener(new View.OnClickListener() { 35 | @Override public void onClick(View v) { 36 | launchNewNoteActivity(); 37 | } 38 | }); 39 | setInitialViews(); 40 | showLoading(); 41 | setData(); 42 | } 43 | 44 | private void launchNewNoteActivity() { 45 | Intent intent = new Intent(getApplicationContext(),NewNoteActivity.class); 46 | startActivityForResult(intent,NEW_NOTE_ADD_REQUEST_CODE); 47 | } 48 | 49 | @Override protected void onResume() { 50 | super.onResume(); 51 | if (notify_adapter){ 52 | mAdapter.setNewData(app.getNotesData().getNotesMap()); 53 | notify_adapter = false; 54 | } 55 | } 56 | 57 | private void setData() { 58 | notes_data = new NotesData(); 59 | this.app.setNotesData(notes_data); 60 | if (notes_data.getNotesMap().size() > 0) { 61 | mAdapter = new DisplayNotesAdapter(DisplayNotesActivity.this, new CardClick(), new DeleteClick()); 62 | activity_binding.recyclerviewNotes.setNestedScrollingEnabled(true); 63 | activity_binding.recyclerviewNotes.setHasFixedSize(true); 64 | activity_binding.recyclerviewNotes.setLayoutManager( 65 | new GridLayoutManager(DisplayNotesActivity.this, 1)); 66 | //activity_binding.recyclerviewNotes.setLayoutManager(new LinearLayoutManager(this.mContext,LinearLayoutManager.HORIZONTAL,false)); 67 | activity_binding.recyclerviewNotes.setAdapter(mAdapter); 68 | mAdapter.setNewData(notes_data.getNotesMap()); 69 | showDataContainer(); 70 | } 71 | else { 72 | showNoData(); 73 | } 74 | } 75 | 76 | private void showNoData() { 77 | activity_binding.progress.setVisibility(View.INVISIBLE); 78 | activity_binding.errorMessageView.setText(getString(R.string.no_data)); 79 | activity_binding.fabAddNote.setVisibility(View.VISIBLE); 80 | if (activity_binding.viewSwitcher.getDisplayedChild() != 0) { 81 | activity_binding.viewSwitcher.showPrevious(); 82 | } 83 | Log.d(TAG, "Showing no data"); 84 | } 85 | 86 | private void setInitialViews() { 87 | setSupportActionBar(activity_binding.toolbar); 88 | app = (HouseLaneApp) getApplication(); 89 | if (getSupportActionBar() != null) { 90 | getSupportActionBar().setTitle(getString(R.string.notes)); 91 | } 92 | } 93 | 94 | private void showLoading() { 95 | activity_binding.fabAddNote.setVisibility(View.INVISIBLE); 96 | activity_binding.progress.setVisibility(View.VISIBLE); 97 | activity_binding.errorMessageView.setText(getString(R.string.message_common_loading)); 98 | if (activity_binding.viewSwitcher.getDisplayedChild() != 0) { 99 | activity_binding.viewSwitcher.showPrevious(); 100 | } 101 | Log.d(TAG, "Showing Loading"); 102 | } 103 | 104 | private void showDataContainer() { 105 | activity_binding.fabAddNote.setVisibility(View.VISIBLE); 106 | if (activity_binding.viewSwitcher.getDisplayedChild() != 1) { 107 | activity_binding.viewSwitcher.showNext(); 108 | } 109 | Log.d(TAG, "Showing Data Container"); 110 | } 111 | 112 | @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { 113 | super.onActivityResult(requestCode, resultCode, data); 114 | if (requestCode==NEW_NOTE_ADD_REQUEST_CODE){ 115 | if (resultCode==RESULT_OK){ 116 | notify_adapter = true; 117 | Log.d(TAG,"Result okay success"); 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | public class CardClick implements View.OnClickListener { 125 | 126 | @Override public void onClick(View v) { 127 | Log.d(TAG, "Card Clicked"); 128 | int position = activity_binding.recyclerviewNotes.getChildAdapterPosition(v); 129 | Long record_id = mAdapter.getRecordId(position); 130 | Intent intent = new Intent(getApplicationContext(),NoteFullViewActivity.class); 131 | intent.putExtra("record_id",record_id); 132 | startActivity(intent); 133 | } 134 | } 135 | 136 | public class DeleteClick implements View.OnClickListener { 137 | 138 | @Override public void onClick(View v) { 139 | // Log.d(TAG,"Delete clicked"); 140 | int position = activity_binding.recyclerviewNotes.getChildAdapterPosition((View) v.getParent().getParent().getParent()); 141 | Long delete_record_id = mAdapter.getRecordId(position); 142 | notes_data.deleteNote(delete_record_id); 143 | app.setNotesData(notes_data); 144 | mAdapter.setNewData(notes_data.getNotesMap()); 145 | mAdapter.notifyDataSetChanged(); 146 | } 147 | } 148 | 149 | } 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/hlanechallenge/activities/NewNoteActivity.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.hlanechallenge.activities; 2 | 3 | import android.content.Intent; 4 | import android.database.Cursor; 5 | import android.databinding.DataBindingUtil; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.Environment; 11 | import android.provider.MediaStore; 12 | import android.support.annotation.Nullable; 13 | import android.support.design.widget.Snackbar; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.support.v7.widget.LinearLayoutManager; 16 | import android.util.Log; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import java.io.File; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import lingaraj.hourglass.in.hlanechallenge.Helpers; 24 | import lingaraj.hourglass.in.hlanechallenge.HouseLaneApp; 25 | import lingaraj.hourglass.in.hlanechallenge.R; 26 | import lingaraj.hourglass.in.hlanechallenge.adapters.NewNoteImageAdapter; 27 | import lingaraj.hourglass.in.hlanechallenge.databinding.ActivityNewNoteBinding; 28 | import lingaraj.hourglass.in.hlanechallenge.model.NotesData; 29 | 30 | /** 31 | * Created by lingaraj on 1/16/18. 32 | * @ Screen 2 33 | */ 34 | 35 | public class NewNoteActivity extends AppCompatActivity 36 | { 37 | private final String TAG = "NEWNOTEACT"; 38 | private NotesData notesData; 39 | private HouseLaneApp app; 40 | private String createdAt; 41 | private ActivityNewNoteBinding activity_binding; 42 | private final int IMAGE_PICK_CODE = 111; 43 | private List images = new ArrayList(); 44 | private NewNoteImageAdapter mAdapter; 45 | @Override 46 | protected void onCreate(@Nullable Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | setContentView(R.layout.activity_new_note); 49 | activity_binding = DataBindingUtil.setContentView(this,R.layout.activity_new_note); 50 | setInitialView(); 51 | 52 | activity_binding.fabAddImage.setOnClickListener(new View.OnClickListener() { 53 | @Override public void onClick(View v) { 54 | Log.d(TAG,"fab button clicked"); 55 | launchGalleryImagePicker(); 56 | } 57 | }); 58 | } 59 | 60 | 61 | 62 | private void setInitialView() { 63 | setSupportActionBar(activity_binding.toolbar); 64 | createdAt = Helpers.noteCreatedAt(); 65 | if (getSupportActionBar()!=null){ 66 | getSupportActionBar().setTitle(getString(R.string.created_at)); 67 | getSupportActionBar().setSubtitle(createdAt); 68 | } 69 | app = (HouseLaneApp) getApplication(); 70 | notesData = app.getNotesData(); 71 | mAdapter = new NewNoteImageAdapter(); 72 | activity_binding.noteImagesRecyclerview.setNestedScrollingEnabled(true); 73 | activity_binding.noteImagesRecyclerview.setHasFixedSize(true); 74 | activity_binding.noteImagesRecyclerview.setLayoutManager(new LinearLayoutManager(getApplicationContext(),LinearLayoutManager.HORIZONTAL,false)); 75 | activity_binding.noteImagesRecyclerview.setAdapter(mAdapter); 76 | 77 | } 78 | 79 | @Override protected void onPause() { 80 | super.onPause(); 81 | app.setNotesData(notesData); 82 | } 83 | 84 | private void launchGalleryImagePicker(){ 85 | Intent intent = new Intent(); 86 | intent.setType("image/*"); 87 | intent.setAction(Intent.ACTION_GET_CONTENT); 88 | startActivityForResult(Intent.createChooser(intent, "Select Picture"),IMAGE_PICK_CODE); 89 | Log.d(TAG,"Gallery Image picker launched"); 90 | } 91 | 92 | @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { 93 | super.onActivityResult(requestCode, resultCode, data); 94 | if (resultCode==RESULT_OK && data!=null){ 95 | if (requestCode==IMAGE_PICK_CODE){ 96 | // String image_uri_string = data.getDataString(); 97 | String path = Helpers.getPath(getApplicationContext(),data.getData()); 98 | Log.d(TAG,"Choosen Image Uri String:"+path); 99 | // Uri uri = Uri.parse(image_uri_string); 100 | // Log.d(TAG,"convert Uri:"+uri.toString()); 101 | 102 | images.add(path); 103 | setAdapterData(); 104 | } 105 | } 106 | } 107 | 108 | private void setAdapterData() { 109 | if (mAdapter.getItemCount()>0){ 110 | mAdapter.notifyDataSetChanged(); 111 | } 112 | else { 113 | mAdapter.setNewData(images); 114 | } 115 | showRecyclerView(); 116 | Log.d(TAG,"Adapter Data Updated"); 117 | } 118 | 119 | @Override 120 | public boolean onCreateOptionsMenu(Menu menu) { 121 | getMenuInflater().inflate(R.menu.menu_save,menu); 122 | return true; 123 | } 124 | 125 | @Override 126 | public boolean onOptionsItemSelected(MenuItem item) { 127 | switch (item.getItemId()){ 128 | case R.id.menu_save: 129 | if (isValidData()){ 130 | addNote(); 131 | Snackbar.make(activity_binding.rootLayout,getString(R.string.note_saved),Snackbar.LENGTH_SHORT).show(); 132 | Intent intent = new Intent(); 133 | setResult(RESULT_OK,intent); 134 | finish(); 135 | 136 | } 137 | else { 138 | Snackbar.make(activity_binding.rootLayout,getString(R.string.validation_error),Snackbar.LENGTH_SHORT).show(); 139 | } 140 | break; 141 | } 142 | return super.onOptionsItemSelected(item); 143 | } 144 | 145 | private void addNote() { 146 | String title = activity_binding.noteTitle.getText().toString(); 147 | String content = activity_binding.noteContent.getText().toString(); 148 | notesData.addNote(title,content,createdAt,images); 149 | } 150 | 151 | private void showRecyclerView() { 152 | activity_binding.noteImagesRecyclerview.setVisibility(View.VISIBLE); 153 | } 154 | 155 | private boolean isValidData(){ 156 | if (activity_binding.noteTitle.getText().toString().isEmpty() || activity_binding.noteTitle.getText().toString().equals(getString(R.string.note_title))){ 157 | return false; 158 | } 159 | else if (activity_binding.noteContent.getText().toString().isEmpty() || activity_binding.noteContent.getText().toString().equals(getString(R.string.note_content))){ 160 | return false; 161 | } 162 | else { 163 | 164 | return true; 165 | } 166 | } 167 | 168 | @Override 169 | public void onBackPressed() { 170 | super.onBackPressed(); 171 | app.setNotesData(notesData); 172 | } 173 | 174 | public String getRealPathFromURI( Uri contentUri) { 175 | Cursor cursor = null; 176 | try { 177 | String[] proj = { MediaStore.Images.Media.DATA }; 178 | cursor = getApplicationContext().getContentResolver().query(contentUri, proj, null, null, null); 179 | int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 180 | cursor.moveToFirst(); 181 | return cursor.getString(column_index); 182 | } finally { 183 | if (cursor != null) { 184 | cursor.close(); 185 | } 186 | } 187 | } 188 | } 189 | --------------------------------------------------------------------------------