-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
188 changed files
with
5,209 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,40 @@ | ||
This is gallery app | ||
<h1 align="center">Gallery App</h1></br> | ||
<p align="center"> | ||
GalleryApp is application show image from Unsplash API, based on MVVM architecture. | ||
</p> | ||
|
||
# GalleryApp | ||
<p align="center"> | ||
|
||
</p> | ||
|
||
## Download | ||
Go to the [Releases](https://github.com/LNMCode/GalleryApp/releases) to download the latest APK. | ||
|
||
## UI Application | ||
|
||
[UI Application](https://www.figma.com/file/abtgGeg11LmHEAgyWqTfTg/Art-gallery-app-UI-(Community)?node-id=0%3A1) - UI of application based on ui shared in Figma. | ||
|
||
## Screenshots | ||
<p align="center"> | ||
<img src="/preview/preview01.gif" width="32%"/> | ||
<img src="/preview/preview02.gif" width="32%"/> | ||
<img src="/preview/preview03.gif" width="32%"/> | ||
</p> | ||
|
||
## Tech stack & Open-source libraries | ||
- Minimum SDK level 21 | ||
- 100% [Kotlin](https://kotlinlang.org/) based + [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) + [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/) for asynchronous. | ||
- JetPack | ||
- [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle) - perform action when lifecycle state changes. | ||
- [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - store and manage UI-related data in a lifecycle conscious way. | ||
- [Room](https://developer.android.com/topic/libraries/architecture/room) - a persistence library provides an abstraction layer over SQLite. | ||
- Architecture | ||
- MVVM Architecture (View - DataBinding - ViewModel - Model) | ||
- Repository pattern | ||
- [Koin](https://github.com/InsertKoinIO/koin) - dependency injection | ||
- Material Design & Animations | ||
- [Retrofit2 & Gson](https://github.com/square/retrofit) - constructing the REST API | ||
- [OkHttp3](https://github.com/square/okhttp) - implementing interceptor, logging and mocking web server | ||
- [Glide](https://github.com/bumptech/glide) - loading images | ||
- [Timber](https://github.com/JakeWharton/timber) - logging | ||
- Shared element container transform/transition between fragments |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
app/src/main/java/com/lnmcode/galleryapp/bindables/BindingActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.lnmcode.galleryapp.bindables | ||
|
||
import androidx.annotation.LayoutRes | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.databinding.DataBindingComponent | ||
import androidx.databinding.DataBindingUtil | ||
import androidx.databinding.ViewDataBinding | ||
|
||
abstract class BindingActivity<T : ViewDataBinding> constructor( | ||
@LayoutRes private val contentLayoutId: Int | ||
) : AppCompatActivity() { | ||
|
||
/** This interface is generated during compilation to contain getters for all used instance `BindingAdapters`. */ | ||
protected var bindingComponent: DataBindingComponent? = DataBindingUtil.getDefaultComponent() | ||
|
||
/** | ||
* A data-binding property will be initialized before being called [onCreate]. | ||
* And inflates using the [contentLayoutId] as a content view for activities. | ||
*/ | ||
@BindingOnly | ||
protected val binding: T by lazy(LazyThreadSafetyMode.NONE) { | ||
DataBindingUtil.setContentView(this, contentLayoutId, bindingComponent) | ||
} | ||
|
||
/** | ||
* An executable inline binding function that receives a binding receiver in lambda. | ||
* | ||
* @param block A lambda block will be executed with the binding receiver. | ||
* @return T A generic class that extends [ViewDataBinding] and generated by DataBinding on compile time. | ||
*/ | ||
@BindingOnly | ||
protected inline fun binding(block: T.() -> Unit): T { | ||
return binding.apply(block) | ||
} | ||
|
||
/** | ||
* Ensures the [binding] property should be executed before being called [onCreate]. | ||
*/ | ||
init { | ||
addOnContextAvailableListener { | ||
binding.notifyChange() | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
app/src/main/java/com/lnmcode/galleryapp/bindables/BindingExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.lnmcode.galleryapp.bindables | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.annotation.LayoutRes | ||
import androidx.databinding.DataBindingUtil | ||
import androidx.databinding.ViewDataBinding | ||
import kotlin.reflect.KFunction | ||
import kotlin.reflect.KProperty | ||
|
||
/** | ||
* | ||
* A binding extension for inflating a [layoutRes] and returns a DataBinding type [T]. | ||
* | ||
* @param layoutRes The layout resource ID of the layout to inflate. | ||
* @param attachToParent Whether the inflated hierarchy should be attached to the parent parameter. | ||
* | ||
* @return T A DataBinding class that inflated using the [layoutRes]. | ||
*/ | ||
@BindingOnly | ||
fun <T : ViewDataBinding> ViewGroup.binding( | ||
@LayoutRes layoutRes: Int, | ||
attachToParent: Boolean = false | ||
): T { | ||
return DataBindingUtil.inflate( | ||
LayoutInflater.from(context), layoutRes, this, attachToParent | ||
) | ||
} | ||
|
||
/** | ||
* | ||
* A binding extension for inflating a [layoutRes] and returns a DataBinding type [T] with a receiver. | ||
* | ||
* @param layoutRes The layout resource ID of the layout to inflate. | ||
* @param attachToParent Whether the inflated hierarchy should be attached to the parent parameter. | ||
* @param block A DataBinding receiver lambda. | ||
* | ||
* @return T A DataBinding class that inflated using the [layoutRes]. | ||
*/ | ||
@BindingOnly | ||
fun <T : ViewDataBinding> ViewGroup.binding( | ||
@LayoutRes layoutRes: Int, | ||
attachToParent: Boolean = false, | ||
block: T.() -> Unit | ||
): T { | ||
return binding<T>(layoutRes, attachToParent).apply(block) | ||
} | ||
|
||
/** | ||
* | ||
* Returns a binding ID by a [KProperty]. | ||
* | ||
* @return A binding resource ID. | ||
*/ | ||
internal fun KProperty<*>.bindingId(): Int { | ||
return BindingManager.getBindingIdByProperty(this) | ||
} | ||
|
||
/** | ||
* | ||
* Returns a binding ID by a [KFunction]. | ||
* | ||
* @return A binding resource ID. | ||
*/ | ||
internal fun KFunction<*>.bindingId(): Int { | ||
return BindingManager.getBindingIdByFunction(this) | ||
} |
65 changes: 65 additions & 0 deletions
65
app/src/main/java/com/lnmcode/galleryapp/bindables/BindingFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.lnmcode.galleryapp.bindables | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.annotation.CallSuper | ||
import androidx.annotation.LayoutRes | ||
import androidx.databinding.DataBindingComponent | ||
import androidx.databinding.DataBindingUtil | ||
import androidx.databinding.ViewDataBinding | ||
import androidx.fragment.app.Fragment | ||
|
||
abstract class BindingFragment<T : ViewDataBinding> constructor( | ||
@LayoutRes private val contentLayoutId: Int | ||
) : Fragment() { | ||
|
||
/** This interface is generated during compilation to contain getters for all used instance `BindingAdapters`. */ | ||
protected var bindingComponent: DataBindingComponent? = DataBindingUtil.getDefaultComponent() | ||
|
||
/** A backing field for providing an immutable [binding] property. */ | ||
private var _binding: T? = null | ||
|
||
/** | ||
* A data-binding property will be initialized in [onCreateView]. | ||
* And provide the inflated view which depends on [contentLayoutId]. | ||
*/ | ||
@BindingOnly | ||
protected val binding: T | ||
get() = checkNotNull(_binding) { | ||
"Fragment $this binding cannot be accessed before onCreateView() or after onDestroyView()" | ||
} | ||
|
||
/** | ||
* An executable inline binding function that receives a binding receiver in lambda. | ||
* | ||
* @param block A lambda block will be executed with the binding receiver. | ||
* @return T A generic class that extends [ViewDataBinding] and generated by DataBinding on compile time. | ||
*/ | ||
@BindingOnly | ||
protected inline fun binding(block: T.() -> Unit): T { | ||
return binding.apply(block) | ||
} | ||
|
||
/** | ||
* Ensures the [binding] property should be executed and provide the inflated view which depends on [contentLayoutId]. | ||
*/ | ||
@CallSuper | ||
override fun onCreateView( | ||
inflater: LayoutInflater, | ||
container: ViewGroup?, | ||
savedInstanceState: Bundle? | ||
): View { | ||
_binding = DataBindingUtil.inflate(inflater, contentLayoutId, container, false, bindingComponent) | ||
return binding.root | ||
} | ||
|
||
/** | ||
* Destroys the [_binding] backing property for preventing leaking the [ViewDataBinding] that references the Context. | ||
*/ | ||
override fun onDestroyView() { | ||
super.onDestroyView() | ||
_binding = null | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
app/src/main/java/com/lnmcode/galleryapp/bindables/BindingManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.lnmcode.galleryapp.bindables | ||
|
||
import androidx.databinding.Bindable | ||
import java.util.* | ||
import kotlin.reflect.KFunction | ||
import kotlin.reflect.KProperty | ||
import kotlin.reflect.full.hasAnnotation | ||
import androidx.databinding.library.baseAdapters.BR | ||
|
||
object BindingManager { | ||
|
||
/** A map for holding information of the generated fields in the BR class. */ | ||
@PublishedApi | ||
internal var bindingFieldsMap: Map<String, Int> = emptyMap() | ||
|
||
/** Java Bean conventions for presenting a boolean. */ | ||
private const val JAVA_BEANS_BOOLEAN: String = "is" | ||
|
||
/** Java Bean conventions for presenting a getter. */ | ||
private const val JAVA_BEANS_GETTER: String = "get" | ||
|
||
/** Java Bean conventions for presenting a setter. */ | ||
private const val JAVA_BEANS_SETTER: String = "set" | ||
|
||
/** | ||
* Binds the `BR` class into the [BindingManager]. | ||
* This method only needs to be called once in the application. | ||
* The `BR` class will be disassembled by the [BindingManager], binding fields will be used | ||
* for finding the proper binding ID of properties. | ||
* | ||
* @param T The `BR` class that generated by the DataBinding processor. | ||
* @return The size of the stored fields. | ||
*/ | ||
inline fun <reified T> bind(): Int { | ||
synchronized(this) { | ||
if (bindingFieldsMap.isNotEmpty()) return@synchronized | ||
bindingFieldsMap = BR::class.java.fields.asSequence() | ||
.map { it.name to it.getInt(null) }.toMap() | ||
} | ||
|
||
return bindingFieldsMap.size | ||
} | ||
|
||
/** | ||
* Returns proper binding ID by property. | ||
* | ||
* @param property A kotlin [androidx.databinding.Bindable] property for finding proper binding ID. | ||
*/ | ||
internal fun getBindingIdByProperty(property: KProperty<*>): Int { | ||
val bindingProperty = property.takeIf { | ||
it.getter.hasAnnotation<Bindable>() | ||
} | ||
?: throw IllegalArgumentException("KProperty: ${property.name} must be annotated with the `@Bindable` annotation on the getter.") | ||
val propertyName = bindingProperty.name.decapitalize(Locale.ENGLISH) | ||
val bindingPropertyName = propertyName | ||
.takeIf { it.startsWith(JAVA_BEANS_BOOLEAN) } | ||
?.replaceFirst(JAVA_BEANS_BOOLEAN, String()) | ||
?.decapitalize(Locale.ENGLISH) ?: propertyName | ||
return bindingFieldsMap[bindingPropertyName] ?: BR._all | ||
} | ||
|
||
/** | ||
* Returns proper binding ID by function. | ||
* | ||
* @param function A kotlin [androidx.databinding.Bindable] function for finding proper binding ID. | ||
*/ | ||
internal fun getBindingIdByFunction(function: KFunction<*>): Int { | ||
val bindingFunction = function.takeIf { | ||
it.hasAnnotation<Bindable>() | ||
} | ||
?: throw IllegalArgumentException("KFunction: ${function.name} must be annotated with the `@Bindable` annotation.") | ||
val functionName = bindingFunction.name.decapitalize(Locale.ENGLISH) | ||
val bindingFunctionName = when { | ||
functionName.startsWith(JAVA_BEANS_GETTER) -> functionName.replaceFirst(JAVA_BEANS_GETTER, String()) | ||
functionName.startsWith(JAVA_BEANS_SETTER) -> functionName.replaceFirst(JAVA_BEANS_SETTER, String()) | ||
functionName.startsWith(JAVA_BEANS_BOOLEAN) -> functionName.replaceFirst(JAVA_BEANS_BOOLEAN, String()) | ||
else -> throw IllegalArgumentException("@Bindable associated with method must follow JavaBeans convention $functionName") | ||
}.decapitalize(Locale.ENGLISH) | ||
return bindingFieldsMap[bindingFunctionName] ?: BR._all | ||
} | ||
} |
Oops, something went wrong.