Skip to content

Commit

Permalink
MEGA COMMITTO
Browse files Browse the repository at this point in the history
  • Loading branch information
HuM4NoiD committed Feb 5, 2021
0 parents commit f9c0f55
Show file tree
Hide file tree
Showing 70 changed files with 1,790 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/*
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
27 changes: 27 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Master Detail Pattern with Jetpack Navigation Component
This is an implementation of Master Detail UI pattern where phones utilize a regular layout but use the extra space available for other views if the space permits.

## Consists of the Following Architectural Patterns and Concepts:
- Single Activity Pattern
- Data Binding and Binding Adapters
- ViewModel Architecture
- Different Layouts for Portrait and Landscape
- Different Layouts for Phones and Tablets
- Jetpack Navigation Component
- Fragment within a Fragment
- Inter-Fragment Communication via ViewModel (Passing Data Between Fragments)
- Dialog and Regular Fragments via Navigation Graphs
- Bottom Navigation Bar
- Bottom Sheet Fragment (Modal Bottom Sheets)
- Multiple Navigation Graphs

## Phone Screenshots
<img src="screens/phoneHome.jpg" width="240">
<img src="screens/phoneList.jpg" width="240">
<img src="screens/phoneDetail.jpg" width="240">
<img src="screens/phoneBottomSheet.jpg" width="240"><br/>

## Tab/Large Screen Screenshots
![Home Fragment](screens/tabHome.png)
![List Master Fragment with Detail Fragment](screens/tabList.png)
![Modal Input Bottom Sheet](screens/tabBottomSheet.png)
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
67 changes: 67 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'kotlin-android-extensions'
id 'androidx.navigation.safeargs.kotlin'
}

android {
compileSdkVersion 30
buildToolsVersion "30.0.2"

defaultConfig {
applicationId "a.b.masterdetail"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

buildFeatures {
dataBinding true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '1.8'
}
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

// Fragment and Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

implementation 'com.github.bumptech.glide:glide:4.12.0'

testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package a.b.masterdetail

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("a.b.masterdetail", appContext.packageName)
}
}
24 changes: 24 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="a.b.masterdetail">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MasterDetail">
<activity
android:name=".ui.activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
3 changes: 3 additions & 0 deletions app/src/main/java/a/b/masterdetail/data/Item.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package a.b.masterdetail.data

data class Item(val name: String, val image: String)
42 changes: 42 additions & 0 deletions app/src/main/java/a/b/masterdetail/ui/activity/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package a.b.masterdetail.ui.activity

import a.b.masterdetail.R
import a.b.masterdetail.databinding.ActivityMainBinding
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.databinding.DataBindingUtil
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.view.*

class MainActivity: AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

val navHost = supportFragmentManager.findFragmentById(R.id.mainFragContainer) as NavHostFragment

binding.bottomNavBar.setupWithNavController(navHost.navController)

navHost.navController.addOnDestinationChangedListener { _, destination, _ ->
supportActionBar?.title = destination.label
when (destination.id) {
R.id.detailFragment -> {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.bottomNavBar.visibility = View.GONE
}
else -> {
if (bottomNavBar.visibility == View.GONE) {
bottomNavBar.visibility = View.VISIBLE
}
}
}
}
}
}
85 changes: 85 additions & 0 deletions app/src/main/java/a/b/masterdetail/ui/adapter/ItemAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package a.b.masterdetail.ui.adapter

import a.b.masterdetail.R
import a.b.masterdetail.data.Item
import a.b.masterdetail.databinding.LayoutItemBinding
import a.b.masterdetail.ui.viewmodel.ItemListViewModel
import android.content.ClipData
import android.graphics.Color
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.menu.MenuView
import androidx.core.content.ContextCompat
import androidx.core.view.get
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModel
import androidx.recyclerview.widget.RecyclerView

class ItemAdapter(
val isLargeScreen: Boolean,
val items: ArrayList<Item>,
val viewModel: ItemListViewModel,
val triggerSelection: (Boolean ,Int, Item) -> Unit
): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

private var selectedItem = viewModel.selectedItemPos

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val binding = DataBindingUtil.inflate<LayoutItemBinding>(
LayoutInflater.from(parent.context),
R.layout.layout_item,
parent,
false
)
return ItemViewHolder(binding.root, binding)
}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.binding.item = items[position]
if (isLargeScreen) {
if (position == selectedItem) {
holder.setColor()
} else {
holder.clearColor()
}
}
}

override fun getItemCount() = items.size

inner class ItemViewHolder(
itemView: View,
val binding: LayoutItemBinding
): RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {

init {
itemView.setOnClickListener(this)
}

override fun onClick(p0: View?) {
if (isLargeScreen) {
viewModel.itemSelected(adapterPosition, items[adapterPosition])
notifyItemChanged(selectedItem)
selectedItem = adapterPosition
notifyItemChanged(adapterPosition)
} else {
triggerSelection(false, adapterPosition, items[adapterPosition])
}
}

fun setColor() {
itemView.setBackgroundColor(Color.parseColor("#4003DAC5"))
}

fun clearColor() {
itemView.setBackgroundColor(Color.parseColor("#00000000"))
}

override fun onLongClick(p0: View?): Boolean {
triggerSelection(true, adapterPosition, items[adapterPosition])
return true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package a.b.masterdetail.ui.dialog

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import a.b.masterdetail.R
import a.b.masterdetail.data.Item
import a.b.masterdetail.databinding.FragmentItemInputDialogBinding
import a.b.masterdetail.ui.viewmodel.ItemListViewModel
import a.b.masterdetail.util.dummyList
import android.util.Log
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

/**
* A simple [Fragment] subclass.
* Use the [ItemInputDialogFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ItemInputDialogFragment: BottomSheetDialogFragment() {

private lateinit var viewModel: ItemListViewModel
private lateinit var binding: FragmentItemInputDialogBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(ItemListViewModel::class.java)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_item_input_dialog, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.submitItem.setOnClickListener {
submitItem(it)
}
}

private fun submitItem(view: View) {
Toast.makeText(requireContext(), "Outside", Toast.LENGTH_SHORT).show()
if (binding.nameInput.length() > 0 && binding.urlInput.length() > 0) {
Log.d("BottomSheet", "onViewCreated: ${binding.nameInput.length()}, ${binding.urlInput.length()}")
Toast.makeText(requireContext(), "Valid Input", Toast.LENGTH_SHORT).show()
val item = Item(binding.nameInput.text.toString(), binding.urlInput.text.toString())
viewModel.newItemAdded(item)
dismiss()
}
}
}
Loading

0 comments on commit f9c0f55

Please sign in to comment.