├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_baseline_home_24.xml │ │ │ │ ├── ic_commit.xml │ │ │ │ ├── ic_profile.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── menu │ │ │ │ └── bottom_menu.xml │ │ │ ├── drawable-v24 │ │ │ │ ├── details_item_background.xml │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── strings.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── navigation │ │ │ │ └── my_nav.xml │ │ │ └── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── shimmer_layout.xml │ │ │ │ ├── fragment_repository.xml │ │ │ │ ├── fragment_commit.xml │ │ │ │ ├── recycler_item_row.xml │ │ │ │ ├── repo_item.xml │ │ │ │ └── fragment_repository_details.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── shakircam │ │ │ │ └── android │ │ │ │ ├── core │ │ │ │ ├── BaseViewModel.kt │ │ │ │ ├── BaseViewHolder.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── BaseActivity.kt │ │ │ │ └── BaseAdapter.kt │ │ │ │ ├── domain │ │ │ │ └── repository │ │ │ │ │ ├── UserRepository.kt │ │ │ │ │ ├── GithubRepository.kt │ │ │ │ │ └── UserRepositoryImp.kt │ │ │ │ ├── model │ │ │ │ ├── User.kt │ │ │ │ ├── Commits.kt │ │ │ │ └── Repository.kt │ │ │ │ ├── data │ │ │ │ ├── local │ │ │ │ │ ├── LocalDataSource.kt │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ ├── UserDao.kt │ │ │ │ │ └── ImageConverter.kt │ │ │ │ └── remote │ │ │ │ │ ├── GithubApi.kt │ │ │ │ │ └── NetworkDataSource.kt │ │ │ │ ├── utils │ │ │ │ ├── Constants.kt │ │ │ │ ├── Resource.kt │ │ │ │ ├── AppPreference.kt │ │ │ │ ├── BindingFragment.kt │ │ │ │ ├── ExtensionFunction.kt │ │ │ │ ├── NetworkListener.kt │ │ │ │ ├── AppPreferenceImpl.kt │ │ │ │ └── BindingData.kt │ │ │ │ ├── app │ │ │ │ └── BaseApp.kt │ │ │ │ ├── di │ │ │ │ ├── RepositoryModule.kt │ │ │ │ ├── DatabaseModule.kt │ │ │ │ ├── WorkManagerInitializer.kt │ │ │ │ └── NetworkModule.kt │ │ │ │ ├── ui │ │ │ │ ├── commit │ │ │ │ │ ├── CommitDiffUtil.kt │ │ │ │ │ ├── CommitAdapter.kt │ │ │ │ │ ├── CommitFragment.kt │ │ │ │ │ └── CommitViewModel.kt │ │ │ │ ├── repo │ │ │ │ │ ├── RepositoryDetailsFragment.kt │ │ │ │ │ ├── RepositoryAdapter.kt │ │ │ │ │ ├── RepositoryViewModel.kt │ │ │ │ │ └── RepositoryFragment.kt │ │ │ │ └── MainActivity.kt │ │ │ │ └── work │ │ │ │ └── TestWorker.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── shakircam │ │ │ └── android │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── shakircam │ │ └── android │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakircam/android-template/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/core/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.core 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | 6 | 7 | open class BaseViewModel(application: Application) : AndroidViewModel(application) { 8 | 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 03 15:37:52 BDT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/core/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.core 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | open class BaseViewHolder (val binding: T) : 7 | RecyclerView.ViewHolder(binding.root) -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.domain.repository 2 | 3 | import com.shakircam.android.model.Commits 4 | import retrofit2.Response 5 | 6 | interface UserRepository { 7 | 8 | suspend fun getGithubCommit(): Response> 9 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity 7 | data class User( 8 | @PrimaryKey(autoGenerate = true) 9 | val id: Int, 10 | val firstName : String, 11 | val lastName:String 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/local/LocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.local 2 | 3 | import com.shakircam.android.model.User 4 | import javax.inject.Inject 5 | 6 | class LocalDataSource @Inject constructor(private val userDao: UserDao) { 7 | 8 | suspend fun insertUserInformation(user: User){ 9 | userDao.insertUserInformation(user) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/local/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.shakircam.android.model.User 6 | 7 | @Database(entities = [User::class],version = 1,exportSchema = false) 8 | abstract class AppDatabase : RoomDatabase() { 9 | 10 | abstract fun userDao() : UserDao 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/local/UserDao.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.local 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import com.shakircam.android.model.User 7 | 8 | @Dao 9 | interface UserDao { 10 | 11 | @Insert(onConflict = OnConflictStrategy.REPLACE) 12 | fun insertUserInformation(user: User) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | 4 | 5 | 6 | 7 | object Constants { 8 | 9 | const val KEY = "key" 10 | const val DATABASE_NAME = "app_database" 11 | 12 | // RETROFIT 13 | const val BASE_URL = "https://api.github.com/" 14 | 15 | // NETWORK CHECK 16 | const val PREFERENCES_BACK_ONLINE = "backOnline" 17 | 18 | 19 | 20 | 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://jitpack.io'} 14 | } 15 | } 16 | rootProject.name = "Android" 17 | include ':app' 18 | -------------------------------------------------------------------------------- /app/src/test/java/com/shakircam/android/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | /** 4 | * 03/07/2022 5 | * Created by Shakir 6 | * Using this class for network call state. 7 | * It will easily manage our network call statement. 8 | */ 9 | 10 | sealed class Resource( 11 | val data: T? = null, 12 | val message: String? = null 13 | ) { 14 | class Success(data: T) : Resource(data) 15 | class Error(message: String, data: T? = null) : Resource(data, message) 16 | class Loading : Resource() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/domain/repository/GithubRepository.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.domain.repository 2 | 3 | import com.shakircam.android.data.local.LocalDataSource 4 | import com.shakircam.android.data.remote.NetworkDataSource 5 | import dagger.hilt.android.scopes.ActivityRetainedScoped 6 | import javax.inject.Inject 7 | 8 | @ActivityRetainedScoped 9 | class GithubRepository @Inject constructor( 10 | networkDataSource: NetworkDataSource, 11 | localDataSource: LocalDataSource 12 | ) { 13 | val remote = networkDataSource 14 | val local = localDataSource 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/details_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/domain/repository/UserRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.domain.repository 2 | 3 | import android.app.Application 4 | import com.shakircam.android.data.remote.GithubApi 5 | import com.shakircam.android.model.Commits 6 | import retrofit2.Response 7 | import javax.inject.Inject 8 | 9 | 10 | /* 11 | 12 | */ 13 | 14 | 15 | class UserRepositoryImp @Inject constructor( 16 | private val githubApi: GithubApi 17 | ) : UserRepository { 18 | 19 | override suspend fun getGithubCommit(): Response> { 20 | return githubApi.getGithubCommit() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/model/Commits.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.model 2 | 3 | data class Commits( 4 | val commit : List 5 | ) { 6 | 7 | data class CommitsItem( 8 | val author: Author, 9 | val commit: Commit, 10 | val sha: String, 11 | val url: String 12 | ) 13 | 14 | data class Author( 15 | val avatar_url: String 16 | ) 17 | 18 | data class Commit( 19 | val author: AuthorX, 20 | val message: String, 21 | ) 22 | 23 | data class AuthorX( 24 | val date: String?, 25 | val email: String, 26 | val name: String 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/model/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.model 2 | import android.os.Parcelable 3 | import kotlinx.parcelize.Parcelize 4 | import kotlinx.parcelize.RawValue 5 | 6 | 7 | data class Repository( 8 | val items: List, 9 | ) { 10 | 11 | @Parcelize 12 | data class Item( 13 | val description: String?, 14 | val full_name: String, 15 | val name: String, 16 | val owner: @RawValue Owner, 17 | val updated_at: String 18 | ): Parcelable 19 | 20 | @Parcelize 21 | data class Owner( 22 | val login: String, 23 | val avatar_url: String 24 | ): Parcelable 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_commit.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/local/ImageConverter.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.local 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | import androidx.room.TypeConverter 6 | import java.io.ByteArrayOutputStream 7 | 8 | class ImageConverter { 9 | @TypeConverter 10 | fun fromBitmap(bitmap: Bitmap): ByteArray { 11 | val outputStream = ByteArrayOutputStream() 12 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) 13 | return outputStream.toByteArray() 14 | } 15 | 16 | @TypeConverter 17 | fun toBitmap(byteArray: ByteArray): Bitmap { 18 | return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/AppPreference.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | import android.content.SharedPreferences 4 | 5 | interface AppPreference { 6 | 7 | 8 | 9 | fun getString(key:String):String ? 10 | fun setString(key:String,value:String) 11 | 12 | fun getInt(key:String):Int ? 13 | fun setInt(key:String,value:Int) 14 | 15 | fun getFloat(key:String):Float ? 16 | fun setFloat(key:String,value:Float) 17 | 18 | fun getLong(key: String):Long ? 19 | fun setLong(key: String,value: Long) 20 | 21 | fun getBoolean(key:String):Boolean ? 22 | fun setBoolean(key:String,value:Boolean) 23 | fun removeValue() 24 | fun deleteSingleValue(key: String) 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FFBB86FC 5 | #FF6200EE 6 | #FF3700B3 7 | #FF03DAC5 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | #333333 12 | #534D4D 13 | #0A84FF 14 | #dddddd 15 | #404040 16 | #B6B0B0 17 | #EEEEEE 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/remote/GithubApi.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.remote 2 | 3 | import com.shakircam.android.model.Commits 4 | import com.shakircam.android.model.Repository 5 | import retrofit2.Response 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface GithubApi { 10 | 11 | @GET("repos/flutter/flutter/commits") 12 | suspend fun getGithubCommit(): Response> 13 | 14 | @GET("search/repositories") 15 | suspend fun getMostStarsRepository( 16 | @Query("q")searchQuery : String, 17 | @Query("sort") sortBy : String, 18 | @Query("order") orderBy : String, 19 | @Query("page")page : Int, 20 | @Query("per_page")perPage : Int, 21 | ):Response 22 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/shakircam/android/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.shakircam.android", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/data/remote/NetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.data.remote 2 | 3 | import com.shakircam.android.model.Commits 4 | import com.shakircam.android.model.Repository 5 | import retrofit2.Response 6 | import javax.inject.Inject 7 | 8 | class NetworkDataSource @Inject constructor(private val githubApi: GithubApi) { 9 | 10 | suspend fun getGithubCommit(): Response> { 11 | return githubApi.getGithubCommit() 12 | } 13 | 14 | suspend fun getMostStarsRepository( 15 | searchQuery: String, 16 | sortBy: String, 17 | orderBy: String, 18 | pageNumber: Int, 19 | perPage: Int 20 | ): Response { 21 | return githubApi.getMostStarsRepository(searchQuery, sortBy, orderBy, pageNumber, perPage) 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/core/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.core 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | 10 | abstract class BaseFragment : Fragment() { 11 | 12 | protected lateinit var binding: VB 13 | protected abstract val viewModel: ViewModel 14 | 15 | abstract fun getViewBinding(): VB 16 | 17 | override fun onCreateView( 18 | inflater: LayoutInflater, 19 | container: ViewGroup?, 20 | savedInstanceState: Bundle? 21 | ): View? { 22 | super.onCreateView(inflater, container, savedInstanceState) 23 | binding = getViewBinding() 24 | return binding.root 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/app/BaseApp.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.app 2 | 3 | import android.app.Application 4 | import androidx.hilt.work.HiltWorkerFactory 5 | import androidx.work.Configuration 6 | import dagger.hilt.android.HiltAndroidApp 7 | import timber.log.Timber 8 | import timber.log.Timber.Forest.plant 9 | import javax.inject.Inject 10 | 11 | @HiltAndroidApp 12 | class BaseApp : Application(), Configuration.Provider { 13 | 14 | @Inject 15 | lateinit var workerFactory: HiltWorkerFactory 16 | 17 | override fun onCreate() { 18 | super.onCreate() 19 | plant(Timber.DebugTree()) 20 | } 21 | 22 | override fun getWorkManagerConfiguration(): Configuration { 23 | return Configuration.Builder().setWorkerFactory(workerFactory) 24 | .setMinimumLoggingLevel(android.util.Log.DEBUG) 25 | .build() 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.di 2 | 3 | import com.shakircam.android.data.local.LocalDataSource 4 | import com.shakircam.android.data.remote.NetworkDataSource 5 | import com.shakircam.android.domain.repository.GithubRepository 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | 14 | 15 | /** 16 | * created at 08/9/2022 17 | */ 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object RepositoryModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideDermaRepo(localDataSource : LocalDataSource, remoteDataSource: NetworkDataSource): GithubRepository { 26 | return GithubRepository(remoteDataSource,localDataSource) 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/commit/CommitDiffUtil.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.commit 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.shakircam.android.model.Commits 5 | 6 | class CommitDiffUtil( 7 | private val oldList: List, 8 | private val newList: List 9 | ): DiffUtil.Callback() { 10 | 11 | 12 | override fun getOldListSize(): Int { 13 | return oldList.size 14 | } 15 | 16 | override fun getNewListSize(): Int { 17 | return newList.size 18 | } 19 | 20 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 21 | return oldList[oldItemPosition] === newList[newItemPosition] 22 | } 23 | 24 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 25 | return oldList[oldItemPosition] == newList[newItemPosition] 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/core/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.core 2 | 3 | import android.content.pm.ActivityInfo 4 | import android.os.Bundle 5 | import androidx.annotation.LayoutRes 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.databinding.ViewDataBinding 9 | 10 | abstract class BaseActivity : AppCompatActivity() { 11 | 12 | @LayoutRes 13 | abstract fun getLayoutRes(): Int 14 | 15 | val binding by lazy { 16 | DataBindingUtil.setContentView(this, getLayoutRes()) as DB 17 | } 18 | 19 | abstract val viewModel: VM 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 24 | 25 | setupBindingLifecycleOwner() 26 | } 27 | 28 | private fun setupBindingLifecycleOwner() { 29 | binding.lifecycleOwner = this 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/repo/RepositoryDetailsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.repo 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.navigation.fragment.navArgs 9 | import com.shakircam.android.databinding.FragmentRepositoryDetailsBinding 10 | 11 | 12 | class RepositoryDetailsFragment : Fragment() { 13 | 14 | private val args by navArgs() 15 | private var _binding: FragmentRepositoryDetailsBinding? = null 16 | private val binding get() = _binding!! 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, container: ViewGroup?, 20 | savedInstanceState: Bundle? 21 | ): View? { 22 | // Inflate the layout for this fragment 23 | _binding = FragmentRepositoryDetailsBinding.inflate(inflater, container, false) 24 | binding.args = args 25 | 26 | return binding.root 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/core/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.core 2 | 3 | import android.view.ViewGroup 4 | import androidx.databinding.ViewDataBinding 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import com.shakircam.android.R 8 | 9 | abstract class BaseAdapter(callback: DiffUtil.ItemCallback) : 10 | ListAdapter>(callback) { 11 | 12 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 13 | (holder as BaseViewHolder<*>).binding.root.setTag(R.string.position, position) 14 | bind(holder.binding, position) 15 | } 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 18 | getViewHolder(parent, viewType) 19 | 20 | open fun getViewHolder(parent: ViewGroup, viewType: Int) = 21 | BaseViewHolder(createBinding(parent, viewType)) 22 | 23 | abstract fun createBinding(parent: ViewGroup, viewType: Int): ViewDataBinding 24 | 25 | protected abstract fun bind(binding: ViewDataBinding, position: Int) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/BindingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | import com.shakircam.android.core.BaseViewModel 10 | 11 | abstract class BindingFragment : Fragment() { 12 | private var _binding : ViewBinding? = null 13 | @Suppress("UNCHECKED_CAST") 14 | protected val binding : T 15 | get() = _binding as T 16 | 17 | 18 | 19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 20 | _binding = bindingInflater(inflater) 21 | return _binding!!.root 22 | 23 | } 24 | 25 | 26 | protected abstract val viewModel: ViewModel 27 | protected abstract val bindingInflater : (LayoutInflater) -> ViewBinding 28 | 29 | override fun onDestroyView() { 30 | super.onDestroyView() 31 | _binding = null 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android 3 | Commits 4 | User Profile 5 | 6 | Hello blank fragment 7 | Flutter Commit List 8 | master 9 | Fixed All bugs 10 | 10:40 11 | Ibrahim 12 | Bio: 13 | Public Repos: 14 | Public Gist: 15 | Private repos: 0 16 | position 17 | Repository 18 | Repository List 19 | Description 20 | Owner 21 | Home 22 | Last update time 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/ExtensionFunction.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.os.Build 8 | import androidx.annotation.RequiresApi 9 | import dagger.hilt.android.internal.Contexts.getApplication 10 | 11 | object ExtensionFunction { 12 | @RequiresApi(Build.VERSION_CODES.M) 13 | fun hasInternetConnection(context: Context): Boolean { 14 | val connectivityManager = getApplication(context).getSystemService( 15 | Context.CONNECTIVITY_SERVICE 16 | ) as ConnectivityManager 17 | val activeNetwork = connectivityManager.activeNetwork ?: return false 18 | val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false 19 | return when { 20 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 21 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 22 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true 23 | else -> false 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.shakircam.android.data.local.AppDatabase 6 | import com.shakircam.android.utils.Constants.DATABASE_NAME 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object DatabaseModule { 18 | 19 | /* 20 | This room database instance is singleton that's mean, whole application will access single 21 | room database instance. 22 | 23 | When ever we need room database instance. 24 | This provide annotation will provide the room db instance dependency. 25 | */ 26 | 27 | @Singleton 28 | @Provides 29 | fun provideDatabase(@ApplicationContext context : Context) 30 | = Room.databaseBuilder(context, 31 | AppDatabase::class.java, 32 | DATABASE_NAME).build() 33 | 34 | @Singleton 35 | @Provides 36 | fun provideDao(database: AppDatabase) = database.userDao() 37 | } -------------------------------------------------------------------------------- /app/src/main/res/navigation/my_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 17 | 20 | 21 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/repo/RepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.repo 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.ViewDataBinding 6 | import androidx.recyclerview.widget.DiffUtil 7 | import com.shakircam.android.core.BaseAdapter 8 | import com.shakircam.android.databinding.RepoItemBinding 9 | import com.shakircam.android.model.Repository 10 | 11 | 12 | class RepositoryAdapter : BaseAdapter(DiffCallback) { 13 | 14 | 15 | override fun createBinding(parent: ViewGroup, viewType: Int): ViewDataBinding { 16 | return RepoItemBinding.inflate( 17 | LayoutInflater.from(parent.context), 18 | parent, 19 | false 20 | ) 21 | } 22 | 23 | override fun bind(binding: ViewDataBinding, position: Int) { 24 | val item = getItem(position) 25 | (binding as RepoItemBinding).repo = item 26 | binding.executePendingBindings() 27 | } 28 | 29 | object DiffCallback : DiffUtil.ItemCallback() { 30 | override fun areItemsTheSame(oldItem: Repository.Item, newItem: Repository.Item): Boolean { 31 | return oldItem.full_name == newItem.full_name 32 | } 33 | 34 | override fun areContentsTheSame(oldItem: Repository.Item, newItem: Repository.Item): Boolean { 35 | return oldItem == newItem 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/di/WorkManagerInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.di 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import androidx.work.Configuration 6 | import androidx.work.WorkManager 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * created at 08/9/2022 16 | * Shakir 17 | * We are trying to inject extra dependency like repository in work manager constructor But by default it's not possible without create custom work manager factory design. 18 | * So we are using dagger hilt factory. We need to update in manifest file & we are keeping tag and also need to add hilt-worker dependency in build.gradle 19 | */ 20 | 21 | @Module 22 | @InstallIn(SingletonComponent::class) 23 | object WorkManagerInitializer : Initializer { 24 | 25 | @Provides 26 | @Singleton 27 | override fun create(@ApplicationContext context: Context): WorkManager { 28 | val configuration = Configuration.Builder().build() 29 | WorkManager.initialize(context, configuration) 30 | return WorkManager.getInstance(context) 31 | } 32 | 33 | override fun dependencies(): List>> { 34 | // No dependencies on other libraries. 35 | return emptyList() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/commit/CommitAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.commit 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.ViewDataBinding 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.shakircam.android.core.BaseAdapter 9 | import com.shakircam.android.databinding.RecyclerItemRowBinding 10 | import com.shakircam.android.model.Commits 11 | 12 | 13 | 14 | class CommitAdapter : BaseAdapter(DiffCallback) { 15 | 16 | 17 | override fun createBinding(parent: ViewGroup, viewType: Int): ViewDataBinding { 18 | 19 | return RecyclerItemRowBinding.inflate( 20 | LayoutInflater.from(parent.context), 21 | parent, 22 | false 23 | ) 24 | } 25 | 26 | override fun bind(binding: ViewDataBinding, position: Int) { 27 | val item = getItem(position) 28 | (binding as RecyclerItemRowBinding).commit = item 29 | binding.executePendingBindings() 30 | } 31 | 32 | 33 | 34 | object DiffCallback : DiffUtil.ItemCallback() { 35 | override fun areItemsTheSame(oldItem: Commits.CommitsItem, newItem: Commits.CommitsItem): Boolean { 36 | return oldItem.author == newItem.author 37 | } 38 | 39 | override fun areContentsTheSame(oldItem: Commits.CommitsItem, newItem: Commits.CommitsItem): Boolean { 40 | return oldItem == newItem 41 | } 42 | 43 | } 44 | 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/NetworkListener.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.Network 6 | import android.net.NetworkCapabilities 7 | import android.os.Build 8 | import androidx.annotation.RequiresApi 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | 11 | class NetworkListener : ConnectivityManager.NetworkCallback(){ 12 | 13 | private val isNetworkAvailable = MutableStateFlow(false) 14 | 15 | 16 | @RequiresApi(Build.VERSION_CODES.N) 17 | fun checkNetworkAvailability(context: Context ) : MutableStateFlow{ 18 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 19 | connectivityManager.registerDefaultNetworkCallback(this) 20 | 21 | var isConnected = false 22 | connectivityManager.allNetworks.forEach {network -> 23 | val networkCapability = connectivityManager.getNetworkCapabilities(network) 24 | networkCapability?.let { 25 | if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)){ 26 | isConnected = true 27 | return@forEach 28 | } 29 | } 30 | } 31 | isNetworkAvailable.value = isConnected 32 | return isNetworkAvailable 33 | } 34 | 35 | 36 | override fun onAvailable(network: Network) { 37 | isNetworkAvailable.value = true 38 | } 39 | 40 | override fun onLost(network: Network) { 41 | isNetworkAvailable.value = false 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.di 2 | 3 | import com.shakircam.android.data.remote.GithubApi 4 | import com.shakircam.android.utils.Constants.BASE_URL 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import okhttp3.OkHttpClient 10 | import retrofit2.Retrofit 11 | import retrofit2.converter.gson.GsonConverterFactory 12 | import java.util.concurrent.TimeUnit 13 | import javax.inject.Singleton 14 | 15 | /** 16 | created at 21/6/2022 17 | Shakir 18 | This is Network module. We are defining this network module will provide the retrofit,okhttp client, factory converter etc... 19 | */ 20 | 21 | 22 | @Module 23 | @InstallIn(SingletonComponent::class) 24 | object NetworkModule { 25 | 26 | @Provides 27 | @Singleton 28 | fun provideOkhttpClient(): OkHttpClient { 29 | return OkHttpClient.Builder() 30 | .readTimeout(15, TimeUnit.SECONDS) 31 | .connectTimeout(15,TimeUnit.SECONDS) 32 | .build() 33 | } 34 | 35 | @Provides 36 | @Singleton 37 | fun provideGsonConverterFactory():GsonConverterFactory{ 38 | return GsonConverterFactory.create() 39 | } 40 | 41 | @Provides 42 | @Singleton 43 | fun provideRetrofitInstance(okHttpClient: OkHttpClient,gsonConverterFactory:GsonConverterFactory) : GithubApi { 44 | return Retrofit.Builder() 45 | .baseUrl(BASE_URL) 46 | .client(okHttpClient) 47 | .addConverterFactory(gsonConverterFactory) 48 | .build() 49 | .create(GithubApi::class.java) 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/work/TestWorker.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.work 2 | 3 | import android.content.Context 4 | import androidx.hilt.work.HiltWorker 5 | import androidx.work.CoroutineWorker 6 | import androidx.work.WorkerParameters 7 | import com.shakircam.android.domain.repository.GithubRepository 8 | import com.shakircam.android.model.User 9 | import dagger.assisted.Assisted 10 | import dagger.assisted.AssistedInject 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.withContext 13 | import timber.log.Timber 14 | 15 | /** 16 | * created at 08/9/2022 17 | * Shakir 18 | * Here we are adding extra dependency(GithubRepository) & now we can call our network client or UserDao. 19 | * If you need any long running background operation you can put your code statement here. 20 | */ 21 | 22 | @HiltWorker 23 | class TestWorker @AssistedInject constructor( 24 | @Assisted appContext: Context, 25 | @Assisted params: WorkerParameters, 26 | private val githubRepository: GithubRepository 27 | ) : CoroutineWorker( 28 | appContext, 29 | params 30 | ) { 31 | 32 | override suspend fun doWork(): Result = withContext(Dispatchers.IO) { 33 | try { 34 | 35 | for (i in 1..20){ 36 | Timber.d("number:$i") 37 | } 38 | 39 | Result.success() 40 | 41 | } catch (e: Exception) { 42 | Result.failure() 43 | } 44 | 45 | } 46 | 47 | private suspend fun fakeNetworkCall() { 48 | githubRepository.remote.getGithubCommit() 49 | } 50 | 51 | private suspend fun addUser() { 52 | val user = User(1,"Jamal","haque") 53 | githubRepository.local.insertUserInformation(user) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Template 2 | Simple app built on top of Dagger Hilt & single activity design with other best practices like MVVM, Navigation, Coroutines, Room, Databinding, Workmanager. 3 | You can just clone it & change the project name & getting most common best practice design. It will save your project initial time 4 | and getting readymate project setup with latest library update. 5 | # Features 6 | - ✅ Mvvm Pattern 7 | - ✅ Base Activity 8 | - ✅ Base Fragment 9 | - ✅ Base View holder 10 | - ✅ Base Adapter 11 | - ✅ Base View Model 12 | - ✅ Repository Pattern 13 | - ✅ Custom Workmanager factory 14 | - ✅ Local Data source 15 | - ✅ Remote Data source 16 | - ✅ Navcontroller 17 | # Libraries & Tools Used 18 | - Dagger Hilt 19 | - Coroutines 20 | - Navigation Component 21 | - ViewModel 22 | - LiveData 23 | - Data Binding 24 | - RoomDB 25 | - Retrofit 26 | - OkHttp 27 | - Glide 28 | - Coil 29 | - Shared preference 30 | - DiffUtil 31 | - Material Design 32 | - Work manager 33 | # Architecture 34 | MVVM (Model View ViewModel) is one of the most popular architectural pattern for Android App development. Basically this pattern separates User interface from business-logic and data-logic. So that it's divided into three layers: Model layer, View layer and View model layer. 35 | ![mvvm](https://developer.android.com/topic/libraries/architecture/images/final-architecture.png) 36 | # How to update app information and continue development for your own project? 37 | - First clone the project 38 | link 39 | - Change the package name 40 | - Change the class name with convenient of project 41 | # Find this repository useful?❤️ 42 | Star this repository and follow me for next creations! Thanks for your support 💗💗. 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/AppPreferenceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | import android.content.Context 4 | 5 | 6 | class AppPreferenceImpl(context: Context): AppPreference { 7 | 8 | private val sharedPreferences = context.getSharedPreferences("app_sharedPreference",0) 9 | private val editor = sharedPreferences.edit() 10 | 11 | 12 | override fun getString(key: String): String? { 13 | return sharedPreferences.getString(key,"Error") 14 | } 15 | 16 | override fun setString(key: String, value: String) { 17 | editor.putString(key,value) 18 | editor.apply() 19 | } 20 | 21 | override fun getInt(key: String):Int? { 22 | return sharedPreferences.getInt(key,-1) 23 | } 24 | 25 | override fun setInt(key: String,value:Int) { 26 | editor.putInt(key,value) 27 | editor.apply() 28 | } 29 | 30 | override fun getFloat(key: String): Float? { 31 | return sharedPreferences.getFloat(key,-0.1F) 32 | } 33 | 34 | override fun setFloat(key: String, value: Float) { 35 | editor.putFloat(key,value) 36 | editor.apply() 37 | } 38 | 39 | override fun getLong(key: String): Long? { 40 | return sharedPreferences.getLong(key,-1) 41 | } 42 | 43 | override fun setLong(key: String, value: Long) { 44 | editor.putLong(key,value) 45 | editor.apply() 46 | } 47 | 48 | override fun getBoolean(key: String): Boolean? { 49 | return sharedPreferences.getBoolean(key,false) 50 | } 51 | 52 | override fun setBoolean(key: String, value: Boolean) { 53 | editor.putBoolean(key,value) 54 | editor.apply() 55 | } 56 | 57 | override fun removeValue() { 58 | editor.clear() 59 | editor.commit() 60 | } 61 | 62 | override fun deleteSingleValue(key: String) { 63 | editor.remove(key) 64 | editor.commit() 65 | } 66 | 67 | 68 | 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/repo/RepositoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.repo 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.os.Build 8 | import androidx.annotation.RequiresApi 9 | import androidx.lifecycle.LiveData 10 | import androidx.lifecycle.MutableLiveData 11 | import androidx.lifecycle.viewModelScope 12 | import com.shakircam.android.core.BaseViewModel 13 | import com.shakircam.android.domain.repository.GithubRepository 14 | import com.shakircam.android.model.Repository 15 | import com.shakircam.android.utils.ExtensionFunction.hasInternetConnection 16 | import com.shakircam.android.utils.Resource 17 | import dagger.hilt.android.lifecycle.HiltViewModel 18 | import kotlinx.coroutines.launch 19 | import retrofit2.Response 20 | import javax.inject.Inject 21 | 22 | 23 | @RequiresApi(Build.VERSION_CODES.M) 24 | @HiltViewModel 25 | class RepositoryViewModel@Inject constructor(private val githubRepository: GithubRepository, application: Application 26 | ) : BaseViewModel(application) { 27 | 28 | 29 | private val _repoResponse : MutableLiveData> = MutableLiveData() 30 | val repoResponse: LiveData> = _repoResponse 31 | var searchQuery = "Android" 32 | var sortBy = "stars" 33 | var orderBy = "asc" 34 | var page = 1 35 | var perPage = 5 36 | 37 | init { 38 | /** Checking internet connection first. if internet is available then we call the getCommmit function. */ 39 | if (hasInternetConnection(application)){ 40 | getRepo() 41 | } 42 | } 43 | 44 | private fun getRepo()= viewModelScope.launch { 45 | _repoResponse.postValue(Resource.Loading()) 46 | val response = githubRepository.remote.getMostStarsRepository(searchQuery,sortBy,orderBy,page,perPage) 47 | _repoResponse.postValue(handleRepoResponse(response)) 48 | } 49 | 50 | private fun handleRepoResponse(response: Response): Resource? { 51 | 52 | if (response.isSuccessful){ 53 | 54 | response.body().let { 55 | return Resource.Success(it!!) 56 | } 57 | } 58 | 59 | return Resource.Error(response.code().toString()) 60 | } 61 | 62 | 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/utils/BindingData.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import androidx.constraintlayout.widget.ConstraintLayout 7 | import androidx.databinding.BindingAdapter 8 | import androidx.navigation.findNavController 9 | import coil.load 10 | import com.bumptech.glide.Glide 11 | import com.shakircam.android.R 12 | import com.shakircam.android.model.Repository 13 | import com.shakircam.android.ui.repo.RepositoryDetailsFragment 14 | import com.shakircam.android.ui.repo.RepositoryFragmentDirections 15 | import java.text.SimpleDateFormat 16 | 17 | class BindingData { 18 | 19 | companion object { 20 | 21 | 22 | /** ----------- data binding for github commits ---------- */ 23 | 24 | @BindingAdapter("loadImageFromUrl") 25 | @JvmStatic 26 | fun loadImageFromUrl(imageView: ImageView, imageUrl: String?) { 27 | imageView.load(imageUrl) { 28 | crossfade(600) 29 | } 30 | } 31 | 32 | @BindingAdapter("loadUserImage") 33 | @JvmStatic 34 | fun loadUserImage(imageView: ImageView, imageUrl: String?) { 35 | Glide.with(imageView.context) 36 | .load(imageUrl) 37 | .placeholder(R.drawable.ic_profile) 38 | .error(R.drawable.ic_profile) 39 | .into(imageView) 40 | } 41 | 42 | @SuppressLint("SimpleDateFormat") 43 | @BindingAdapter("timeFormat") 44 | @JvmStatic 45 | fun timeFormat(textView: TextView, date: String) { 46 | 47 | val parser = SimpleDateFormat("yyyy-MM-dd") 48 | val formatter = SimpleDateFormat("MM/dd/yyyy") 49 | val formattedDate = formatter.format(parser.parse(date)!!) 50 | textView.text = formattedDate 51 | } 52 | 53 | @BindingAdapter("android:sendDataToDetailsFragment") 54 | @JvmStatic 55 | fun sendDataToDetailsFragment(view: ConstraintLayout, currentItem: Repository.Item){ 56 | view.setOnClickListener { 57 | val action = RepositoryFragmentDirections.actionRepositoryFragmentToRepositoryDetailsFragment(currentItem) 58 | view.findNavController().navigate(action) 59 | } 60 | } 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/shimmer_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 18 | 19 | 28 | 29 | 38 | 39 | 48 | 49 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_repository.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.view.MenuItem 6 | import android.view.View 7 | import androidx.navigation.NavController 8 | import androidx.navigation.findNavController 9 | import androidx.navigation.fragment.NavHostFragment 10 | import androidx.navigation.ui.* 11 | import androidx.work.OneTimeWorkRequestBuilder 12 | import androidx.work.WorkManager 13 | import androidx.work.WorkRequest 14 | import com.shakircam.android.R 15 | import com.shakircam.android.databinding.ActivityMainBinding 16 | import com.shakircam.android.work.TestWorker 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | 20 | @AndroidEntryPoint 21 | class MainActivity : AppCompatActivity() { 22 | 23 | private lateinit var binding: ActivityMainBinding 24 | private lateinit var navController: NavController 25 | lateinit var appBarConfiguration: AppBarConfiguration 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | binding = ActivityMainBinding.inflate(layoutInflater) 30 | setContentView(binding.root) 31 | 32 | initWorkManager() 33 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment 34 | navController = navHostFragment.navController 35 | 36 | //bottom nav 37 | binding.bottomNavigationView.setupWithNavController(navController) 38 | appBarConfiguration = AppBarConfiguration( 39 | setOf( 40 | R.id.repositoryFragment, 41 | R.id.commitFragment, 42 | ) 43 | ) 44 | 45 | // connect appbar with nav controller 46 | setupActionBarWithNavController(navController, appBarConfiguration) 47 | 48 | //hide bottom navigation in specific fragment 49 | navController.addOnDestinationChangedListener { _, destination, _ -> 50 | when (destination.id) { 51 | 52 | R.id.repositoryDetailsFragment -> 53 | binding.bottomNavigationView.visibility = View.GONE 54 | 55 | else -> 56 | binding.bottomNavigationView.visibility = View.VISIBLE 57 | } 58 | } 59 | } 60 | 61 | private fun initWorkManager() { 62 | val request: WorkRequest = OneTimeWorkRequestBuilder() 63 | .build() 64 | val workManager = WorkManager.getInstance(this) 65 | workManager.enqueue(request) 66 | } 67 | 68 | // toolbar back btn pressed 69 | override fun onSupportNavigateUp(): Boolean { 70 | return findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration) 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/repo/RepositoryFragment.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.repo 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import androidx.annotation.RequiresApi 8 | import androidx.core.view.isVisible 9 | import androidx.fragment.app.viewModels 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import androidx.viewbinding.ViewBinding 12 | import com.facebook.shimmer.ShimmerFrameLayout 13 | import com.shakircam.android.databinding.FragmentRepositoryBinding 14 | import com.shakircam.android.utils.BindingFragment 15 | import com.shakircam.android.utils.Resource 16 | import dagger.hilt.android.AndroidEntryPoint 17 | import timber.log.Timber 18 | 19 | @AndroidEntryPoint 20 | class RepositoryFragment : BindingFragment() { 21 | 22 | private val adapter by lazy { RepositoryAdapter() } 23 | private lateinit var shimmerFrameLayout: ShimmerFrameLayout 24 | 25 | override val bindingInflater: (LayoutInflater) -> ViewBinding 26 | get() = FragmentRepositoryBinding::inflate 27 | 28 | override val viewModel: RepositoryViewModel by viewModels() 29 | 30 | 31 | @RequiresApi(Build.VERSION_CODES.M) 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | shimmerFrameLayout = binding.shimmerLayout 35 | initRecyclerView() 36 | getRepoList() 37 | 38 | } 39 | 40 | override fun onResume() { 41 | super.onResume() 42 | shimmerFrameLayout.startShimmer() 43 | 44 | } 45 | 46 | override fun onPause() { 47 | shimmerFrameLayout.stopShimmer() 48 | super.onPause() 49 | } 50 | 51 | private fun initRecyclerView() { 52 | val mRecyclerView = binding.recyclerView 53 | mRecyclerView.adapter = adapter 54 | mRecyclerView.layoutManager = LinearLayoutManager(requireActivity(), 55 | LinearLayoutManager.VERTICAL,false) 56 | } 57 | 58 | 59 | @RequiresApi(Build.VERSION_CODES.M) 60 | private fun getRepoList(){ 61 | viewModel.repoResponse.observe(viewLifecycleOwner){ response-> 62 | 63 | when(response){ 64 | is Resource.Success -> { 65 | response.data?.let { commitsResponse -> 66 | Timber.d("owner name: " + response.data) 67 | shimmerFrameLayout.isVisible = false 68 | adapter.submitList(commitsResponse.items) 69 | } 70 | } 71 | 72 | is Resource.Error -> { 73 | response.message?.let { message -> 74 | Timber.e("An error occurred: $message") 75 | } 76 | } 77 | is Resource.Loading -> { 78 | shimmerFrameLayout.isVisible = true 79 | } 80 | } 81 | } 82 | } 83 | 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_commit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 32 | 33 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recycler_item_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 18 | 19 | 31 | 32 | 41 | 42 | 53 | 54 | 65 | 66 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/repo_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 16 | 17 | 29 | 30 | 39 | 40 | 51 | 52 | 63 | 64 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/commit/CommitFragment.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.commit 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import androidx.annotation.RequiresApi 9 | import androidx.core.view.isVisible 10 | import androidx.fragment.app.viewModels 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import androidx.viewbinding.ViewBinding 13 | import com.facebook.shimmer.ShimmerFrameLayout 14 | import com.shakircam.android.databinding.FragmentCommitBinding 15 | import com.shakircam.android.model.Commits 16 | import com.shakircam.android.utils.BindingFragment 17 | import com.shakircam.android.utils.Resource 18 | import dagger.hilt.android.AndroidEntryPoint 19 | import timber.log.Timber 20 | 21 | 22 | @AndroidEntryPoint 23 | class CommitFragment : BindingFragment() { 24 | 25 | private val adapter by lazy { CommitAdapter() } 26 | private lateinit var shimmerFrameLayout: ShimmerFrameLayout 27 | 28 | 29 | 30 | override val bindingInflater: (LayoutInflater) -> ViewBinding 31 | get() = FragmentCommitBinding::inflate 32 | 33 | 34 | 35 | @RequiresApi(Build.VERSION_CODES.M) 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | 39 | shimmerFrameLayout = binding.shimmerLayout 40 | 41 | initRecyclerView() 42 | requestApiData() 43 | getRepoList() 44 | } 45 | 46 | 47 | @RequiresApi(Build.VERSION_CODES.M) 48 | private fun getRepoList(){ 49 | viewModel.repoResponse.observe(viewLifecycleOwner){ 50 | Timber.d("tag","owner name: ${it.data}") 51 | } 52 | } 53 | 54 | @RequiresApi(Build.VERSION_CODES.M) 55 | private fun requestApiData(){ 56 | viewModel.commitsResponse.observe(viewLifecycleOwner){ response -> 57 | 58 | when(response){ 59 | is Resource.Success -> { 60 | response.data?.let { commitsResponse -> 61 | shimmerFrameLayout.isVisible = false 62 | adapter.submitList(commitsResponse) 63 | } 64 | } 65 | 66 | is Resource.Error -> { 67 | 68 | response.message?.let { message -> 69 | Timber.e("An error occurred: $message") 70 | } 71 | } 72 | is Resource.Loading -> shimmerFrameLayout.isVisible = true 73 | } 74 | } 75 | } 76 | 77 | override fun onResume() { 78 | super.onResume() 79 | shimmerFrameLayout.startShimmer() 80 | 81 | } 82 | 83 | override fun onPause() { 84 | shimmerFrameLayout.stopShimmer() 85 | super.onPause() 86 | } 87 | 88 | 89 | private fun initRecyclerView() { 90 | val mRecyclerView = binding.recyclerView 91 | mRecyclerView.adapter = adapter 92 | mRecyclerView.layoutManager = LinearLayoutManager(requireActivity(), LinearLayoutManager.VERTICAL,false) 93 | } 94 | 95 | 96 | 97 | override val viewModel: CommitViewModel by viewModels() 98 | 99 | 100 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | id 'kotlin-parcelize' 7 | id 'androidx.navigation.safeargs' 8 | } 9 | 10 | android { 11 | compileSdk 33 12 | 13 | defaultConfig { 14 | applicationId "com.shakircam.android" 15 | minSdk 21 16 | targetSdk 33 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | buildFeatures { 31 | viewBinding true 32 | dataBinding true 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | } 43 | 44 | dependencies { 45 | 46 | implementation 'androidx.core:core-ktx:1.8.0' 47 | implementation 'androidx.appcompat:appcompat:1.5.0' 48 | implementation 'com.google.android.material:material:1.6.1' 49 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 50 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 51 | testImplementation 'junit:junit:4.13.2' 52 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 54 | 55 | // ViewModel and LiveData 56 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" 57 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" 58 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" 59 | implementation 'androidx.fragment:fragment-ktx:1.5.2' 60 | 61 | // Navigation 62 | implementation "androidx.navigation:navigation-fragment-ktx:2.5.1" 63 | implementation "androidx.navigation:navigation-ui-ktx:2.5.1" 64 | 65 | // Kotlin components 66 | api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1" 67 | api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" 68 | 69 | // Room components 70 | implementation "androidx.room:room-runtime:2.4.3" 71 | kapt "androidx.room:room-compiler:2.4.3" 72 | implementation "androidx.room:room-ktx:2.4.3" 73 | androidTestImplementation "androidx.room:room-testing:2.4.3" 74 | 75 | // Retrofit 76 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 77 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 78 | implementation "com.squareup.okhttp3:logging-interceptor:4.9.1" 79 | implementation "com.squareup.retrofit2:converter-moshi:2.7.1" 80 | 81 | //Dagger Hilt 82 | implementation 'com.google.dagger:hilt-android:2.42' 83 | kapt 'com.google.dagger:hilt-compiler:2.42' 84 | implementation 'androidx.hilt:hilt-work:1.0.0' 85 | kapt 'androidx.hilt:hilt-compiler:1.0.0' 86 | implementation "androidx.startup:startup-runtime:1.1.1" 87 | 88 | // work manager 89 | implementation "androidx.work:work-runtime-ktx:2.7.1" 90 | 91 | // image loading and caching 92 | implementation 'com.github.bumptech.glide:glide:4.13.0' 93 | annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' 94 | //coil image loading 95 | implementation("io.coil-kt:coil:1.1.1") 96 | 97 | // DataStore 98 | implementation "androidx.datastore:datastore-preferences:1.0.0" 99 | 100 | // Paging 3 101 | implementation "androidx.paging:paging-runtime:3.1.1" 102 | implementation("androidx.room:room-paging:2.4.3") 103 | 104 | 105 | //Timber log 106 | implementation 'com.jakewharton.timber:timber:5.0.1' 107 | 108 | //Shimmer Effect 109 | implementation 'com.facebook.shimmer:shimmer:0.5.0' 110 | 111 | // Rounded image view 112 | implementation 'de.hdodenhof:circleimageview:3.1.0' 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakircam/android/ui/commit/CommitViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.shakircam.android.ui.commit 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.os.Build 8 | import androidx.annotation.RequiresApi 9 | import androidx.lifecycle.LiveData 10 | import androidx.lifecycle.MutableLiveData 11 | import androidx.lifecycle.viewModelScope 12 | import com.shakircam.android.core.BaseViewModel 13 | import com.shakircam.android.domain.repository.GithubRepository 14 | import com.shakircam.android.model.Commits 15 | import com.shakircam.android.model.Repository 16 | import com.shakircam.android.utils.ExtensionFunction.hasInternetConnection 17 | import com.shakircam.android.utils.Resource 18 | import dagger.hilt.android.lifecycle.HiltViewModel 19 | import kotlinx.coroutines.launch 20 | import retrofit2.Response 21 | import javax.inject.Inject 22 | 23 | @RequiresApi(Build.VERSION_CODES.M) 24 | @HiltViewModel 25 | class CommitViewModel @Inject constructor(private val githubRepository: GithubRepository, application: Application 26 | ) : BaseViewModel(application) { 27 | 28 | /** RETROFIT */ 29 | /** ------ github commits call ----- */ 30 | 31 | 32 | private val _commitsResponse : MutableLiveData>> = MutableLiveData() 33 | val commitsResponse: LiveData>> = _commitsResponse 34 | 35 | 36 | 37 | private val _repoResponse : MutableLiveData> = MutableLiveData() 38 | val repoResponse: LiveData> = _repoResponse 39 | var searchQuery = "Android" 40 | var sortBy = "stars" 41 | var orderBy = "asc" 42 | var page = 1 43 | var perPage = 5 44 | 45 | init { 46 | /** Checking internet connection first. if internet is available then we call the getCommit function. */ 47 | if (hasInternetConnection(application)){ 48 | getRepo() 49 | getCommit() 50 | } 51 | } 52 | 53 | private fun getRepo()= viewModelScope.launch { 54 | _repoResponse.postValue(Resource.Loading()) 55 | val response = githubRepository.remote.getMostStarsRepository(searchQuery,sortBy,orderBy,page,perPage) 56 | _repoResponse.postValue(handleRepoResponse(response)) 57 | } 58 | 59 | private fun handleRepoResponse(response: Response): Resource? { 60 | 61 | if (response.isSuccessful){ 62 | 63 | response.body().let { 64 | return Resource.Success(it!!) 65 | } 66 | } 67 | 68 | return Resource.Error(response.code().toString()) 69 | } 70 | 71 | private fun getCommit() = viewModelScope.launch { 72 | _commitsResponse.postValue(Resource.Loading()) 73 | val response = githubRepository.remote.getGithubCommit() 74 | _commitsResponse.postValue(handleCommitsResponse(response)) 75 | } 76 | 77 | 78 | private fun handleCommitsResponse(response: Response>) : Resource> { 79 | if(response.isSuccessful) { 80 | response.body()?.let { resultResponse -> 81 | 82 | val filterList = mutableListOf() 83 | val updatedList = mutableListOf() 84 | 85 | /** we are checking user name starts with g & x .If match with condition then we are keeping those a list.After that removing from main list. 86 | And finally keeping only last 10 commits */ 87 | 88 | 89 | for (element in resultResponse){ 90 | if (element.commit.author.name.startsWith("g") || element.commit.author.name.startsWith("x")){ 91 | filterList.add(element) 92 | } 93 | } 94 | 95 | resultResponse.removeAll(filterList) 96 | var commitItem = 0 97 | for (item in resultResponse){ 98 | 99 | if (commitItem<10){ 100 | updatedList.add(item) 101 | commitItem++ 102 | } 103 | } 104 | return Resource.Success(updatedList) 105 | } 106 | 107 | } 108 | return Resource.Error(response.message()) 109 | } 110 | 111 | 112 | 113 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_repository_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 29 | 30 | 36 | 37 | 46 | 47 | 50 | 51 | 61 | 62 | 73 | 74 | 84 | 85 | 96 | 97 | 107 | 108 | 119 | 120 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | --------------------------------------------------------------------------------