├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── van.jpg │ │ │ │ ├── instagramlogo.png │ │ │ │ ├── ic_home.xml │ │ │ │ ├── ic_check.xml │ │ │ │ ├── ic_home_black_24dp.xml │ │ │ │ ├── login_input_bg.xml │ │ │ │ ├── ic_close.xml │ │ │ │ ├── bg_map.xml │ │ │ │ ├── border_bg.xml │ │ │ │ ├── dialog_bg.xml │ │ │ │ ├── ic_comment.xml │ │ │ │ ├── ic_share.xml │ │ │ │ ├── ic_baseline_close_24.xml │ │ │ │ ├── ic_dashboard_black_24dp.xml │ │ │ │ ├── ic_profile.xml │ │ │ │ ├── button_bg.xml │ │ │ │ ├── ic_fav.xml │ │ │ │ ├── ic_exit.xml │ │ │ │ ├── ic_fav_red.xml │ │ │ │ ├── ic_more.xml │ │ │ │ ├── button_border.xml │ │ │ │ ├── ic_notifications_black_24dp.xml │ │ │ │ ├── ic_search.xml │ │ │ │ ├── ic_baseline_account_circle_24.xml │ │ │ │ ├── ic_contacts.xml │ │ │ │ ├── ic_like.xml │ │ │ │ ├── login_btn.xml │ │ │ │ ├── ic_baseline_share_24.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── van_picture.jpg │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── values │ │ │ │ ├── dimen.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── styles.xml │ │ │ ├── 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 │ │ │ ├── color │ │ │ │ └── login_btn_color.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── activity_second_host.xml │ │ │ │ ├── my_posts_item.xml │ │ │ │ ├── fragment_contacts.xml │ │ │ │ ├── stories_recycler_row.xml │ │ │ │ ├── bottom_navigation_bar.xml │ │ │ │ ├── dialog_password.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── top_bar.xml │ │ │ │ ├── contacts_item.xml │ │ │ │ ├── following_item.xml │ │ │ │ ├── fragment_followers.xml │ │ │ │ ├── followers_item.xml │ │ │ │ ├── fragment_get_user_location.xml │ │ │ │ ├── dialog_view.xml │ │ │ │ ├── fragment_home.xml │ │ │ │ ├── likes_item.xml │ │ │ │ ├── fragment_following.xml │ │ │ │ ├── fragment_likes.xml │ │ │ │ ├── add_friends_item.xml │ │ │ │ ├── fragment_search.xml │ │ │ │ ├── fragment_login.xml │ │ │ │ ├── feed_recycler_row.xml │ │ │ │ ├── fragment_share.xml │ │ │ │ ├── fragment_register.xml │ │ │ │ └── fragment_edit.xml │ │ │ ├── animator │ │ │ │ └── login_btn_animator.xml │ │ │ ├── menu │ │ │ │ └── menu.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── navigation │ │ │ │ └── graph.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── suveybesena │ │ │ │ └── instagramclone │ │ │ │ ├── model │ │ │ │ ├── LikesModel.kt │ │ │ │ ├── FollowModel.kt │ │ │ │ ├── MyPostsModel.kt │ │ │ │ ├── FollowersModel.kt │ │ │ │ ├── User.kt │ │ │ │ ├── RegisterModel.kt │ │ │ │ └── Firebase.kt │ │ │ │ ├── presentation │ │ │ │ ├── likes │ │ │ │ │ ├── LikesInterface.kt │ │ │ │ │ ├── UnlikeInterface.kt │ │ │ │ │ ├── SwipeGesture.kt │ │ │ │ │ ├── LikesAdapter.kt │ │ │ │ │ ├── LikesViewModel.kt │ │ │ │ │ └── LikesFragment.kt │ │ │ │ ├── search │ │ │ │ │ ├── FollowInterface.kt │ │ │ │ │ ├── UnfollowInterface.kt │ │ │ │ │ ├── GetUserInterface.kt │ │ │ │ │ ├── FriendsAdapter.kt │ │ │ │ │ └── SearchFragment.kt │ │ │ │ ├── home │ │ │ │ │ ├── FeedLikesInterface.kt │ │ │ │ │ ├── FeedAdapter.kt │ │ │ │ │ └── HomeFragment.kt │ │ │ │ ├── profile │ │ │ │ │ ├── MyPostsAdapter.kt │ │ │ │ │ ├── ProfileViewModel.kt │ │ │ │ │ └── ProfileFragment.kt │ │ │ │ ├── following │ │ │ │ │ ├── FollowingAdapter.kt │ │ │ │ │ ├── FollowingFragment.kt │ │ │ │ │ └── FollowingViewModel.kt │ │ │ │ ├── followers │ │ │ │ │ ├── FollowersAdapter.kt │ │ │ │ │ ├── FollowersFragment.kt │ │ │ │ │ └── FollowerViewModel.kt │ │ │ │ ├── contacts │ │ │ │ │ ├── ContactsAdapter.kt │ │ │ │ │ └── ContactsFragment.kt │ │ │ │ ├── login │ │ │ │ │ ├── LoginViewModel.kt │ │ │ │ │ └── LoginFragment.kt │ │ │ │ ├── edit │ │ │ │ │ ├── EditViewModel.kt │ │ │ │ │ └── EditFragment.kt │ │ │ │ ├── map │ │ │ │ │ └── GetUserLocationFragment.kt │ │ │ │ ├── share │ │ │ │ │ ├── ShareViewModel.kt │ │ │ │ │ └── ShareFragment.kt │ │ │ │ └── register │ │ │ │ │ ├── RegisterViewModel.kt │ │ │ │ │ └── RegisterFragment.kt │ │ │ │ ├── di │ │ │ │ ├── MyApplication.kt │ │ │ │ └── FirebaseModule.kt │ │ │ │ ├── utils │ │ │ │ └── extensions │ │ │ │ │ ├── ImageViewExt.kt │ │ │ │ │ └── Resources.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── suveybesena │ │ │ └── instagramclone │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── suveybesena │ │ └── instagramclone │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro ├── google-services.json └── build.gradle ├── .idea ├── .gitignore ├── compiler.xml ├── vcs.xml ├── render.experimental.xml └── gradle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/van.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/drawable/van.jpg -------------------------------------------------------------------------------- /app/src/main/res/van_picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/van_picture.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/instagramlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/drawable/instagramlogo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29f 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suveybesena/InstagramClone/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/suveybesena/InstagramClone/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/suveybesena/InstagramClone/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/suveybesena/InstagramClone/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/suveybesena/InstagramClone/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 16dp 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/LikesModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | data class LikesModel ( 4 | var postImage : String 5 | 6 | 7 | ){ 8 | } -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/FollowModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | data class FollowModel( 4 | var name: String?, 5 | var image: String? 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/MyPostsModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | class MyPostsModel 4 | ( 5 | var image: String, 6 | var id: String 7 | ) { 8 | } -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/FollowersModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | data class FollowersModel( 4 | var name: String?, 5 | var image: String? 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | data class User( 4 | var username: String?, 5 | var image: String?, 6 | var uid: String? 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/LikesInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 2 | 3 | 4 | 5 | interface LikesInterface { 6 | fun likeItemClick( image : String) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/UnlikeInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 2 | 3 | interface UnlikeInterface { 4 | 5 | fun unlikeItemClick( image : String) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/search/FollowInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.search 2 | 3 | interface FollowInterface { 4 | 5 | fun itemOnClick(userId : String) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/search/UnfollowInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.search 2 | 3 | interface UnfollowInterface { 4 | 5 | fun onItemClick(userId : String) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/di/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.di 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class MyApplication : Application() { 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 14 20:17:03 TRT 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/suveybesena/instagramclone/presentation/home/FeedLikesInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.home 2 | 3 | import android.widget.ImageView 4 | 5 | interface FeedLikesInterface { 6 | 7 | fun getLikes (image : String, like :ImageView, unlike :ImageView) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/search/GetUserInterface.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.search 2 | 3 | import android.widget.Button 4 | 5 | interface GetUserInterface { 6 | 7 | fun getUserList (userId : String, follow : Button, unfollow : Button) 8 | } -------------------------------------------------------------------------------- /app/src/main/res/color/login_btn_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/RegisterModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | import android.net.Uri 4 | import android.view.View 5 | 6 | data class RegisterModel ( 7 | var mail: String, 8 | var password: String, 9 | var pickedImage: Uri, 10 | var view: View, 11 | var username: String, 12 | var name : String 13 | ){ 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/login_input_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 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 | } 14 | } 15 | rootProject.name = "InstagramClone" 16 | include ':app' 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/model/Firebase.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.model 2 | 3 | data class Firebase (var image: String, 4 | var comment : String, 5 | var user : String, 6 | var userImage: String, 7 | var location : String, 8 | var allInOne : HashMap) { 9 | 10 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/utils/extensions/ImageViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.utils.extensions 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | 6 | fun ImageView.downloadImage(imageUrl:String){ 7 | try { 8 | Glide.with(this.context).load(imageUrl).into(this) 9 | } catch (e:Exception){ 10 | println(e.localizedMessage) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dialog_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_comment.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/utils/extensions/Resources.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.utils.extensions 2 | 3 | sealed class Resources ( 4 | val data :T? = null, 5 | val message : String? =null 6 | ) { 7 | class Success (data: T) :Resources(data) 8 | class error(message: String, data: T? = null) : Resources(data, message) 9 | class loading : Resources() 10 | 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | > 11 | -------------------------------------------------------------------------------- /app/src/test/java/com/suveybesena/instagramclone/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone 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/res/drawable/ic_fav.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exit.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fav_red.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_second_host.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | InstagramClone 3 | 4 | Hello blank fragment 5 | suveybesena 6 | MainActivity2 7 | Home 8 | Dashboard 9 | Notifications 10 | MapsActivity 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/animator/login_btn_animator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/my_posts_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #E7E7E7 11 | #1767F1 12 | #7C7C7C 13 | #A11B1B 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contacts.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_like.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_contacts.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/login_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_share_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/SwipeGesture.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 2 | 3 | 4 | import androidx.recyclerview.widget.ItemTouchHelper 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | 8 | abstract class SwipeToDeleteCallBack :ItemTouchHelper.Callback(){ 9 | override fun getMovementFlags( 10 | recyclerView: RecyclerView, 11 | viewHolder: RecyclerView.ViewHolder 12 | ): Int { 13 | val swipeFlag = ItemTouchHelper.LEFT 14 | return makeMovementFlags(0, swipeFlag) 15 | } 16 | 17 | override fun onMove( 18 | recyclerView: RecyclerView, 19 | viewHolder: RecyclerView.ViewHolder, 20 | target: RecyclerView.ViewHolder 21 | ): Boolean { 22 | return false 23 | } 24 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /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/androidTest/java/com/suveybesena/instagramclone/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone 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.suveybesena.instagramclone", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/stories_recycler_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_navigation_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 18 | 23 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_password.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.di 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.google.firebase.storage.FirebaseStorage 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | 15 | object FirebaseModule { 16 | 17 | @Provides 18 | @Singleton 19 | fun provideFirebaseAuthInstance() : FirebaseAuth = FirebaseAuth.getInstance() 20 | 21 | @Provides 22 | @Singleton 23 | fun provideFirebaseFirestoreInstance() : FirebaseFirestore = FirebaseFirestore.getInstance() 24 | 25 | @Provides 26 | @Singleton 27 | fun provideFirebaseStorageInstance() : FirebaseStorage= FirebaseStorage.getInstance() 28 | 29 | 30 | 31 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "797543189652", 4 | "project_id": "instagramclone-1b368", 5 | "storage_bucket": "instagramclone-1b368.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:797543189652:android:e0db1639087bc2ae4cf881", 11 | "android_client_info": { 12 | "package_name": "com.suveybesena.instagramclone" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "797543189652-5v92v3q52u4coh5n167u0c7ddfqkj281.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyBef6JdgQrKRlwOvajDilt9HymY9N2ZoZA" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "797543189652-5v92v3q52u4coh5n167u0c7ddfqkj281.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/profile/MyPostsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.profile 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.suveybesena.instagramclone.R 9 | import com.suveybesena.instagramclone.model.MyPostsModel 10 | import kotlinx.android.synthetic.main.my_posts_item.view.* 11 | 12 | 13 | class MyPostsAdapter(var myPostList: List) : 14 | RecyclerView.Adapter() { 15 | class MyPostsVH(itemView: View) : RecyclerView.ViewHolder(itemView) { 16 | } 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyPostsVH { 19 | val inflater = LayoutInflater.from(parent.context) 20 | val view = inflater.inflate(R.layout.my_posts_item, parent, false) 21 | return MyPostsVH(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: MyPostsVH, position: Int) { 25 | 26 | Glide.with(holder.itemView).load(myPostList[position].image) 27 | .into(holder.itemView.iw_my_posts) 28 | 29 | 30 | } 31 | 32 | override fun getItemCount(): Int { 33 | return myPostList.size 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/LikesAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.suveybesena.instagramclone.R 9 | import com.suveybesena.instagramclone.model.LikesModel 10 | import com.suveybesena.instagramclone.utils.extensions.downloadImage 11 | import kotlinx.android.synthetic.main.likes_item.view.* 12 | 13 | class LikesAdapter( 14 | var likesList: List, 15 | ): RecyclerView.Adapter() { 16 | class LikesVH (itemView : View) : RecyclerView.ViewHolder(itemView) { 17 | } 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LikesVH { 20 | val inflater = LayoutInflater.from(parent.context) 21 | val view = inflater.inflate(R.layout.likes_item, parent, false) 22 | return LikesVH(view) 23 | } 24 | 25 | override fun onBindViewHolder(holder: LikesVH, position: Int) { 26 | 27 | holder.itemView.iw_likes.downloadImage(likesList[position].postImage) 28 | 29 | 30 | } 31 | 32 | override fun getItemCount(): Int { 33 | return likesList.size 34 | } 35 | 36 | 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/following/FollowingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.following 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.suveybesena.instagramclone.R 9 | import com.suveybesena.instagramclone.model.FollowModel 10 | import com.suveybesena.instagramclone.utils.extensions.downloadImage 11 | import kotlinx.android.synthetic.main.add_friends_item.view.* 12 | 13 | class FollowingAdapter(var list: List) : RecyclerView.Adapter() { 14 | class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { 15 | 16 | } 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { 19 | val view = 20 | LayoutInflater.from(parent.context).inflate(R.layout.following_item, parent, false) 21 | return VH(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: VH, position: Int) { 25 | val user = list[position] 26 | holder.itemView.apply { 27 | tw_name.text = user.name 28 | user.image?.let { iw_user.downloadImage(it) } 29 | } 30 | 31 | } 32 | 33 | override fun getItemCount(): Int { 34 | return list.size 35 | } 36 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

InstagramClone

2 | 3 | In this project, I made an Instagram clone. I used firebase auth, firestore database and storage, I wrote it according to mvvm architecture. 4 | 5 | 6 |

Libraries Used

7 |
    8 |
  • Firebase Authentication
  • 9 |
  • Firebase Storage
  • 10 |
  • Firebase Firestore
  • 11 |
  • Viewbinding
  • 12 |
  • Hilt
  • 13 |
  • Bottom navigation bar
  • 14 |
  • Glide
  • 15 |
  • Google Maps SDK
  • 16 |
17 |

Architecture

18 |
    19 |
  • ViewModel
  • 20 |
  • LiveData
  • 21 |
  • Navigation
  • 22 |
23 |

App Images

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/followers/FollowersAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.followers 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.suveybesena.instagramclone.R 8 | import com.suveybesena.instagramclone.model.FollowersModel 9 | import com.suveybesena.instagramclone.utils.extensions.downloadImage 10 | import kotlinx.android.synthetic.main.add_friends_item.view.* 11 | 12 | class FollowersAdapter(var list: List) : 13 | RecyclerView.Adapter() { 14 | class FollowersVH(itemView: View) : RecyclerView.ViewHolder(itemView) { 15 | 16 | } 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowersVH { 19 | val inflater = LayoutInflater.from(parent.context) 20 | val view = inflater.inflate(R.layout.followers_item, parent, false) 21 | return FollowersVH(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: FollowersVH, position: Int) { 25 | val user = list[position] 26 | holder.itemView.apply { 27 | tw_name.text = user.name 28 | user.image.toString()?.let { iw_user.downloadImage(it) } 29 | } 30 | 31 | } 32 | 33 | override fun getItemCount(): Int { 34 | return list.size 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/contacts/ContactsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.contacts 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.suveybesena.instagramclone.R 8 | import kotlinx.android.synthetic.main.add_friends_item.view.* 9 | 10 | class ContactsAdapter ( var list : ArrayList) :RecyclerView.Adapter() { 11 | class ContactsVH(itemView : View) :RecyclerView.ViewHolder(itemView){ 12 | } 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactsVH { 14 | val inflater = LayoutInflater.from(parent.context) 15 | val view = inflater.inflate(R.layout.contacts_item, parent, false) 16 | return ContactsVH(view) 17 | } 18 | 19 | override fun onBindViewHolder(holder: ContactsVH, position: Int) { 20 | val contacts = list[position] 21 | 22 | holder.itemView.apply { 23 | tw_name.text = contacts 24 | setOnClickListener { 25 | onItemClickListener?.let { it(contacts) } 26 | } 27 | } 28 | } 29 | 30 | override fun getItemCount(): Int { 31 | return list.size 32 | } 33 | 34 | private var onItemClickListener: ((String) -> Unit)? = null 35 | fun setOnItemClickListener(listener: (String) -> Unit) { 36 | onItemClickListener = listener 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/top_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 17 | 25 | 26 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.login 2 | 3 | 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import com.google.firebase.storage.FirebaseStorage 9 | import com.suveybesena.instagramclone.di.FirebaseModule 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class LoginViewModel @Inject 15 | constructor( 16 | var authInstance: FirebaseAuth 17 | ) : ViewModel() { 18 | 19 | private val loadingState = MutableLiveData() 20 | val _loadingState = loadingState 21 | private val errorState = MutableLiveData() 22 | val _errorState = errorState 23 | 24 | 25 | fun login(email: String, password: String) { 26 | loadingState.value = true 27 | if (validate(email, password)) { 28 | authInstance.signInWithEmailAndPassword(email, password) 29 | .addOnSuccessListener { 30 | loadingState.value = false 31 | } 32 | .addOnFailureListener { error -> 33 | loadingState.value = false 34 | errorState.value = error.localizedMessage 35 | } 36 | } 37 | } 38 | 39 | private fun validate(email: String, password: String): Boolean { 40 | email.isNotEmpty() && password.isNotEmpty() 41 | return true 42 | } 43 | 44 | } 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/contacts_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 18 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 13 | 14 | 19 | 20 | 26 | 27 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/following_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 23 | 24 | 25 | 26 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_followers.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 15 | 16 | 22 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/followers_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 16 | 22 | 23 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_get_user_location.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 27 | 28 | 39 | 40 | -------------------------------------------------------------------------------- /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/suveybesena/instagramclone/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone 2 | 3 | 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.view.Menu 7 | import android.view.MenuItem 8 | import androidx.navigation.NavController 9 | import androidx.navigation.Navigation 10 | import androidx.navigation.findNavController 11 | import androidx.navigation.fragment.NavHostFragment 12 | import androidx.navigation.ui.AppBarConfiguration 13 | import androidx.navigation.ui.setupActionBarWithNavController 14 | import androidx.navigation.ui.setupWithNavController 15 | import com.google.android.material.bottomnavigation.BottomNavigationView 16 | import com.google.firebase.auth.FirebaseAuth 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import kotlinx.android.synthetic.main.top_bar.* 19 | @AndroidEntryPoint 20 | class MainActivity : AppCompatActivity() { 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_main) 25 | 26 | this.supportActionBar?.hide() 27 | 28 | val navHostFragment = 29 | supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment 30 | val navController: NavController = navHostFragment.navController 31 | 32 | val bottomNavigationView = findViewById(R.id.bottom_navigation_view) 33 | val appBarConfiguration = AppBarConfiguration( 34 | setOf( 35 | R.id.shareFragment, R.id.profileFragment,R.id.searchFragment, R.id.homeFragment, R.id.likesFragment 36 | ) 37 | ) 38 | setupActionBarWithNavController(navController, appBarConfiguration) 39 | bottomNavigationView.setupWithNavController(navController) 40 | 41 | 42 | 43 | 44 | 45 | } 46 | 47 | 48 | 49 | 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/followers/FollowersFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.followers 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.fragment.app.viewModels 9 | import com.google.android.material.snackbar.Snackbar 10 | import com.suveybesena.instagramclone.databinding.FragmentFollowersBinding 11 | import com.suveybesena.instagramclone.model.FollowersModel 12 | import dagger.hilt.android.AndroidEntryPoint 13 | 14 | @AndroidEntryPoint 15 | class FollowersFragment : Fragment() { 16 | private val viewModel: FollowerViewModel by viewModels() 17 | private lateinit var followersAdapter: FollowersAdapter 18 | private lateinit var binding: FragmentFollowersBinding 19 | 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | } 24 | 25 | override fun onCreateView( 26 | inflater: LayoutInflater, container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View? { 29 | binding = FragmentFollowersBinding.inflate(inflater) 30 | return binding.root 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | 36 | viewModel.getFollowersId() 37 | observeLiveData() 38 | } 39 | 40 | private fun observeLiveData() { 41 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 42 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 43 | } 44 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 45 | // 46 | } 47 | 48 | viewModel._followerState.observe(viewLifecycleOwner) { list -> 49 | adapter(list) 50 | } 51 | 52 | } 53 | 54 | private fun adapter(list: List) { 55 | followersAdapter = FollowersAdapter(list) 56 | binding.followersRecyclerView.adapter = followersAdapter 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/search/FriendsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.search 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.Button 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.suveybesena.instagramclone.R 9 | import com.suveybesena.instagramclone.model.User 10 | import com.suveybesena.instagramclone.utils.extensions.downloadImage 11 | import kotlinx.android.synthetic.main.add_friends_item.view.* 12 | 13 | class FriendsAdapter( 14 | var list: List, 15 | var followInterface: FollowInterface, 16 | var unfollowInterface: UnfollowInterface, 17 | var getUserInterface : GetUserInterface 18 | ) : 19 | RecyclerView.Adapter() { 20 | class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { 21 | 22 | } 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { 25 | val view = 26 | LayoutInflater.from(parent.context).inflate(R.layout.add_friends_item, parent, false) 27 | return VH(view) 28 | } 29 | 30 | override fun onBindViewHolder(holder: VH, position: Int) { 31 | val user = list[position] 32 | val userId = user.uid.toString() 33 | val image = user.image 34 | holder.itemView.apply { 35 | tw_name.text = user.username 36 | user.image?.let { imageString -> iw_user.downloadImage(imageString) } 37 | 38 | checkFollowingStatus(bw_follow, userId, bw_unfollow) 39 | 40 | bw_follow.setOnClickListener { 41 | if (image != null) { 42 | followInterface.itemOnClick(userId) 43 | } 44 | } 45 | bw_unfollow.setOnClickListener { 46 | unfollowInterface.onItemClick(userId) 47 | 48 | } 49 | } 50 | } 51 | 52 | 53 | fun checkFollowingStatus(followButton : Button,userId : String, unfollowButton : Button ){ 54 | getUserInterface.getUserList(userId, followButton, unfollowButton) 55 | } 56 | 57 | override fun getItemCount(): Int { 58 | return list.size 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/following/FollowingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.following 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.fragment.app.viewModels 9 | import com.google.android.material.snackbar.Snackbar 10 | import com.suveybesena.instagramclone.databinding.FragmentFollowingBinding 11 | import com.suveybesena.instagramclone.model.FollowModel 12 | import dagger.hilt.android.AndroidEntryPoint 13 | 14 | @AndroidEntryPoint 15 | class FollowingFragment : Fragment() { 16 | 17 | private val viewModel: FollowingViewModel by viewModels() 18 | private lateinit var followingadapter: FollowingAdapter 19 | private lateinit var binding : FragmentFollowingBinding 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | } 25 | 26 | override fun onCreateView( 27 | inflater: LayoutInflater, container: ViewGroup?, 28 | savedInstanceState: Bundle? 29 | ): View { 30 | binding = FragmentFollowingBinding.inflate(inflater) 31 | return binding.root 32 | } 33 | 34 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 35 | super.onViewCreated(view, savedInstanceState) 36 | 37 | viewModel.getFollowersId() 38 | observeLiveData() 39 | } 40 | 41 | private fun observeLiveData() { 42 | viewModel._userList.observe(viewLifecycleOwner) { list -> 43 | feedAdapter(list) 44 | } 45 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 46 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 47 | } 48 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 49 | binding.followingProgressBar.visibility = if (loading == true) View.VISIBLE else View.GONE 50 | } 51 | 52 | 53 | } 54 | private fun feedAdapter(list: List) { 55 | followingadapter = FollowingAdapter(list) 56 | binding.followRecyclerView.adapter = followingadapter 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/LikesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.google.firebase.firestore.FirebaseFirestore 7 | import com.google.firebase.storage.FirebaseStorage 8 | import com.suveybesena.instagramclone.model.LikesModel 9 | import com.suveybesena.instagramclone.di.FirebaseModule 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | import kotlin.collections.ArrayList 13 | @HiltViewModel 14 | class LikesViewModel @Inject 15 | constructor(var firebaseInstance : FirebaseFirestore, 16 | var authInstance : FirebaseAuth 17 | ): ViewModel() { 18 | 19 | private var likesList = MutableLiveData?>() 20 | var _likesList = likesList 21 | private var errorState = MutableLiveData() 22 | var _errorState = errorState 23 | private var loadingState = MutableLiveData() 24 | var _loadingState = loadingState 25 | 26 | 27 | 28 | var tempList = ArrayList() 29 | 30 | 31 | fun loadImage() { 32 | loadingState.value = true 33 | 34 | tempList.clear() 35 | firebaseInstance.collection("UsersName").document(authInstance.currentUser?.uid.toString()) 36 | .addSnapshotListener { value, exception -> 37 | if (exception != null) { 38 | likesList.value = null 39 | loadingState.value = false 40 | errorState.value = exception.localizedMessage 41 | } else { 42 | errorState.value = null 43 | val image = value?.get("likes") as List? 44 | image?.forEach { images-> 45 | val list = LikesModel(images) 46 | tempList.add(list) 47 | } 48 | likesList.value = tempList 49 | loadingState.value = false 50 | } 51 | } 52 | 53 | 54 | } 55 | 56 | 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/edit/EditViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.edit 2 | 3 | 4 | import android.net.Uri 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import com.google.firebase.auth.FirebaseAuth 8 | import com.google.firebase.firestore.FirebaseFirestore 9 | import com.google.firebase.storage.FirebaseStorage 10 | import com.suveybesena.instagramclone.di.FirebaseModule 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class EditViewModel @Inject constructor(var firebaseInstance : FirebaseFirestore, 16 | var storageInstance : FirebaseStorage, 17 | var authInstance : FirebaseAuth) : ViewModel() { 18 | 19 | 20 | private val loadingState = MutableLiveData() 21 | var _loadingState = loadingState 22 | private val errorState = MutableLiveData() 23 | val _errorState = errorState 24 | 25 | 26 | fun saveEdit(name: String, surname: String, bio: String, webSite: String, pickedImage: Uri) { 27 | var currentUserId = authInstance.currentUser?.uid.toString() 28 | storageInstance.reference.child("images").child(currentUserId).delete() 29 | val storage = 30 | storageInstance.reference.child("images").child(currentUserId) 31 | storage.putFile(pickedImage) 32 | storage.downloadUrl.addOnSuccessListener { uri -> 33 | val imageUrl = uri.toString() 34 | 35 | 36 | 37 | firebaseInstance.collection("UsersName").document(currentUserId) 38 | .update( 39 | mapOf( 40 | "name" to name, 41 | "surname" to surname, 42 | "bio" to bio, 43 | "website" to webSite, 44 | "image" to imageUrl 45 | 46 | ) 47 | ).addOnCompleteListener { loadingState.value = false } 48 | .addOnFailureListener { error -> 49 | loadingState.value = false 50 | errorState.value = error.localizedMessage 51 | } 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 25 | 26 | 38 | 39 | 40 | 41 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 17 | 18 | 29 | 30 | 40 | 41 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/home/FeedAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.home 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.suveybesena.instagramclone.presentation.likes.LikesInterface 8 | import com.suveybesena.instagramclone.R 9 | import com.suveybesena.instagramclone.model.Firebase 10 | import com.suveybesena.instagramclone.presentation.likes.UnlikeInterface 11 | import com.suveybesena.instagramclone.utils.extensions.downloadImage 12 | import kotlinx.android.synthetic.main.feed_recycler_row.view.* 13 | 14 | 15 | class FeedAdapter( 16 | var list: List, 17 | var likeInterface: LikesInterface, 18 | var getLikesInterface: FeedLikesInterface, 19 | var unlikeInterface: UnlikeInterface 20 | ) : RecyclerView.Adapter() { 21 | class FeedVH(itemView: View) : RecyclerView.ViewHolder(itemView) { 22 | 23 | } 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedVH { 26 | val inflater = LayoutInflater.from(parent.context) 27 | val view = inflater.inflate(R.layout.feed_recycler_row, parent, false) 28 | return FeedVH(view) 29 | } 30 | 31 | override fun onBindViewHolder(holder: FeedVH, position: Int) { 32 | val user = list[position] 33 | val image = user.image 34 | 35 | holder.itemView.apply { 36 | tw_comment.text = user.comment 37 | tw_username_comment.text = user.user 38 | tw_username_feed.text = user.location 39 | imw_post.downloadImage(user.image) 40 | iw_user_profile_feed.downloadImage(user.allInOne[user.image].toString()) 41 | 42 | } 43 | val like = holder.itemView.imw_like 44 | val unlike = holder.itemView.imw_unLike 45 | getLikesInterface.getLikes(list.get(position).image, like, unlike) 46 | 47 | holder.itemView.imw_like.setOnClickListener { 48 | likeInterface.likeItemClick(image) 49 | } 50 | holder.itemView.imw_unLike.setOnClickListener { 51 | unlikeInterface.unlikeItemClick(image) 52 | } 53 | 54 | } 55 | 56 | override fun getItemCount(): Int { 57 | return list.size 58 | } 59 | 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/likes_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 17 | 24 | 35 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_following.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 19 | 20 | 26 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_likes.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 25 | 26 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/map/GetUserLocationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.map 2 | 3 | import android.location.Geocoder 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.navigation.Navigation 10 | import com.google.android.gms.maps.GoogleMap 11 | import com.google.android.gms.maps.OnMapReadyCallback 12 | import com.google.android.gms.maps.SupportMapFragment 13 | import com.google.android.gms.maps.model.LatLng 14 | import com.suveybesena.instagramclone.R 15 | import com.suveybesena.instagramclone.databinding.FragmentGetUserLocationBinding 16 | import kotlinx.android.synthetic.main.fragment_get_user_location.* 17 | import java.util.* 18 | 19 | class GetUserLocationFragment : Fragment(), OnMapReadyCallback { 20 | 21 | private lateinit var mMap: GoogleMap 22 | private lateinit var binding : FragmentGetUserLocationBinding 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, 26 | container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View? { 29 | binding =FragmentGetUserLocationBinding.inflate(inflater) 30 | return binding.root 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? 36 | mapFragment?.getMapAsync(this) 37 | } 38 | 39 | override fun onMapReady(googleMap: GoogleMap) { 40 | mMap = googleMap 41 | mMap.setOnMapLongClickListener { latLng -> 42 | mapClickListener(latLng) 43 | } 44 | } 45 | 46 | private fun mapClickListener(location: LatLng) { 47 | mMap.clear() 48 | val geocoder = Geocoder(requireContext(), Locale.getDefault()) 49 | var address = "" 50 | try { 51 | val adressList = geocoder.getFromLocation(location.latitude, location.longitude, 1) 52 | if (adressList.get(0).subAdminArea != null) { 53 | address += adressList.get(0).subAdminArea 54 | if (adressList.get(0).adminArea != null) { 55 | address += adressList.get(0).adminArea 56 | } 57 | } 58 | 59 | } catch (e: Exception) { 60 | e.printStackTrace() 61 | } 62 | binding.twLocation.text = address 63 | binding.bwCheck.setOnClickListener { 64 | val action = 65 | GetUserLocationFragmentDirections.actionGetUserLocationFragmentToShareFragment() 66 | action.location = address 67 | Navigation.findNavController(requireView()).navigate(action) 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/likes/LikesFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.likes 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.fragment.app.viewModels 9 | import androidx.recyclerview.widget.ItemTouchHelper 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.suveybesena.instagramclone.databinding.FragmentLikesBinding 13 | import com.suveybesena.instagramclone.model.LikesModel 14 | import dagger.hilt.android.AndroidEntryPoint 15 | import kotlinx.android.synthetic.main.fragment_likes.* 16 | 17 | @AndroidEntryPoint 18 | class LikesFragment : Fragment() { 19 | 20 | private val viewModel: LikesViewModel by viewModels() 21 | private lateinit var likesAdapter: LikesAdapter 22 | private lateinit var binding: FragmentLikesBinding 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | } 27 | 28 | override fun onCreateView( 29 | inflater: LayoutInflater, container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View? { 32 | binding = FragmentLikesBinding.inflate(inflater) 33 | return binding.root 34 | } 35 | 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | viewModel.loadImage() 39 | observeLiveData() 40 | } 41 | 42 | private fun observeLiveData() { 43 | viewModel._likesList.observe(viewLifecycleOwner) { list -> 44 | if (list != null) adapterInit(list) 45 | } 46 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 47 | if (error != null) { 48 | Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 49 | } 50 | } 51 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 52 | binding.likesProgressBar.visibility = if (loading == true) View.VISIBLE else View.GONE 53 | 54 | } 55 | } 56 | 57 | private fun adapterInit(list: List) { 58 | likesAdapter = LikesAdapter(list) 59 | binding.likesRecyclerView.adapter = likesAdapter 60 | 61 | val swipe = object : SwipeToDeleteCallBack() { 62 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { 63 | val position = viewHolder.adapterPosition 64 | 65 | binding.likesRecyclerView.adapter?.notifyItemRemoved(position) 66 | } 67 | 68 | } 69 | 70 | val itemTouchHelper = ItemTouchHelper(swipe) 71 | itemTouchHelper.attachToRecyclerView(likes_recyclerView) 72 | 73 | 74 | } 75 | 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/share/ShareViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.share 2 | 3 | import android.net.Uri 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import com.google.firebase.storage.FirebaseStorage 9 | import com.suveybesena.instagramclone.di.FirebaseModule 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class ShareViewModel @Inject 15 | constructor(var firebaseInstance : FirebaseFirestore, 16 | var storageInstance : FirebaseStorage, 17 | var authInstance : FirebaseAuth 18 | ) 19 | : ViewModel() { 20 | 21 | 22 | private val loadingState = MutableLiveData() 23 | val _loadingState = loadingState 24 | private val errorState = MutableLiveData() 25 | val _errorState = errorState 26 | 27 | fun imageDownloader(pickedImage: Uri, comment: String, location: String) { 28 | loadingState.value = true 29 | val currentUserId = authInstance.currentUser?.uid.toString() 30 | 31 | val imageReference = storageInstance.reference.child("feedImages").child(currentUserId) 32 | 33 | imageReference.putFile(pickedImage).addOnSuccessListener { taskSnapshot -> 34 | 35 | val uploadedImageReference = 36 | FirebaseStorage.getInstance().reference.child("feedImages").child(currentUserId) 37 | uploadedImageReference.downloadUrl.addOnSuccessListener { uri -> 38 | val imageUrl = uri.toString() 39 | val currentUserMail = authInstance.currentUser?.email.toString() 40 | val date = com.google.firebase.Timestamp.now() 41 | 42 | val postHashmap = hashMapOf() 43 | postHashmap.put("downloadUri", imageUrl) 44 | postHashmap.put("currentUserMail", currentUserMail) 45 | postHashmap.put("comment", comment) 46 | postHashmap.put("date", date) 47 | postHashmap.put("uid", currentUserId) 48 | postHashmap.put("location", location) 49 | 50 | firebaseInstance.collection("feedImages").add(postHashmap).addOnCompleteListener { task -> 51 | if (task.isSuccessful) { 52 | loadingState.value = false 53 | } 54 | }.addOnFailureListener { exception -> 55 | loadingState.value = false 56 | errorState.value = exception.localizedMessage 57 | } 58 | 59 | }.addOnFailureListener { exception -> 60 | loadingState.value = false 61 | errorState.value = exception.localizedMessage 62 | } 63 | } 64 | } 65 | 66 | 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/following/FollowingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.following 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.google.firebase.firestore.FirebaseFirestore 7 | import com.google.firebase.storage.FirebaseStorage 8 | import com.suveybesena.instagramclone.model.FollowModel 9 | import com.suveybesena.instagramclone.di.FirebaseModule 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class FollowingViewModel 15 | @Inject constructor( 16 | var firebaseInstance : FirebaseFirestore, 17 | var authInstance : FirebaseAuth 18 | ) 19 | : ViewModel() { 20 | 21 | 22 | private val userList = MutableLiveData>() 23 | val _userList = userList 24 | private val errorState = MutableLiveData() 25 | val _errorState = errorState 26 | private val loadingState = MutableLiveData() 27 | val _loadingState = loadingState 28 | 29 | 30 | var tempList = ArrayList() 31 | var name = "" 32 | var image = "" 33 | 34 | fun getFollowersId() { 35 | var currentUserId = authInstance.currentUser?.uid 36 | 37 | if (currentUserId!= null) { 38 | firebaseInstance.collection("UsersName").document(currentUserId.toString()) 39 | .addSnapshotListener { value, error -> 40 | if (error != null) { 41 | errorState.value = error.localizedMessage 42 | } 43 | val doc = value?.get("following") as List? 44 | if (doc != null) { 45 | if (doc.isNotEmpty()) { 46 | getFollowersProfiles(doc) 47 | } 48 | } 49 | } 50 | } 51 | 52 | } 53 | 54 | private fun getFollowersProfiles(followerIdList: List) { 55 | loadingState.value = true 56 | tempList.clear() 57 | followerIdList.forEach { followerId -> 58 | firebaseInstance.collection("UsersName").document(followerId) 59 | .addSnapshotListener { value, error -> 60 | if (error != null) { 61 | loadingState.value = false 62 | errorState.value = error.localizedMessage 63 | } 64 | if (value != null) { 65 | val image = value.getString("image") 66 | val name = value.getString("name") 67 | tempList.add(FollowModel(name, image)) 68 | userList.postValue(tempList) 69 | loadingState.value = false 70 | } 71 | } 72 | 73 | } 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/followers/FollowerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.followers 2 | 3 | 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import com.google.firebase.storage.FirebaseStorage 9 | import com.suveybesena.instagramclone.model.FollowersModel 10 | import com.suveybesena.instagramclone.di.FirebaseModule 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import javax.inject.Inject 13 | @HiltViewModel 14 | class FollowerViewModel @Inject constructor( 15 | var firebaseInstance : FirebaseFirestore, 16 | var authInstance : FirebaseAuth 17 | ): ViewModel() { 18 | 19 | private val followerState = MutableLiveData>() 20 | var _followerState = followerState 21 | private val loadingState = MutableLiveData() 22 | val _loadingState = loadingState 23 | private val errorState = MutableLiveData() 24 | val _errorState = errorState 25 | 26 | 27 | 28 | var tempList = ArrayList() 29 | var name = "" 30 | var image = "" 31 | 32 | fun getFollowersId() { 33 | 34 | val currentUserId = authInstance.currentUser?.uid 35 | if (currentUserId != null) { 36 | firebaseInstance.collection("UsersName").document(currentUserId.toString()) 37 | .addSnapshotListener { value, error -> 38 | if (error != null) { 39 | errorState.value = error.localizedMessage 40 | } 41 | val doc = value?.get("followers") as List? 42 | if (doc != null) { 43 | getFollowersProfiles(doc) 44 | } else { 45 | 46 | errorState.value = error?.localizedMessage 47 | } 48 | } 49 | 50 | } 51 | 52 | } 53 | 54 | private fun getFollowersProfiles(followerIdList: List) { 55 | loadingState.value = true 56 | tempList.clear() 57 | followerIdList.forEach { followerId -> 58 | firebaseInstance.collection("UsersName").document(followerId) 59 | .addSnapshotListener { value, error -> 60 | if (error != null) { 61 | loadingState.value = false 62 | errorState.value = error.localizedMessage 63 | } 64 | if (value != null) { 65 | val image = value.getString("image") 66 | val name = value.getString("name") 67 | tempList.add(FollowersModel(name, image)) 68 | followerState.postValue(tempList) 69 | loadingState.value = false 70 | } 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /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/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-android-extensions' 5 | id 'androidx.navigation.safeargs' 6 | id 'com.google.gms.google-services' 7 | id 'kotlin-kapt' 8 | id 'dagger.hilt.android.plugin' 9 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' 10 | } 11 | 12 | android { 13 | compileSdk 32 14 | 15 | defaultConfig { 16 | applicationId "com.suveybesena.instagramclone" 17 | minSdk 23 18 | targetSdk 32 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | buildFeatures { 39 | viewBinding true 40 | } 41 | } 42 | 43 | dependencies { 44 | 45 | implementation 'androidx.core:core-ktx:1.7.0' 46 | implementation 'androidx.appcompat:appcompat:1.4.1' 47 | implementation 'com.google.android.material:material:1.5.0' 48 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 49 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 50 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 51 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 52 | implementation 'com.google.android.gms:play-services-maps:18.0.2' 53 | testImplementation 'junit:junit:4.13.2' 54 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 55 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 56 | 57 | //Navigation 58 | def nav_version = "2.4.1" 59 | implementation("androidx.navigation:navigation-fragment-ktx:$nav_version") 60 | implementation("androidx.navigation:navigation-ui-ktx:$nav_version") 61 | 62 | //bottom navigation 63 | implementation 'com.google.android.material:material:' 64 | 65 | //glide 66 | implementation 'com.github.bumptech.glide:glide:4.11.0' 67 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 68 | implementation 'de.hdodenhof:circleimageview:2.2.0' 69 | 70 | //firebase 71 | implementation platform('com.google.firebase:firebase-bom:29.1.0') 72 | implementation 'com.google.firebase:firebase-auth-ktx' 73 | implementation 'com.google.firebase:firebase-firestore-ktx' 74 | implementation 'com.google.firebase:firebase-storage-ktx' 75 | 76 | //Picasso 77 | implementation 'com.squareup.picasso:picasso:2.71828' 78 | 79 | //dagger-hilt 80 | implementation "com.google.dagger:hilt-android:2.38.1" 81 | kapt "com.google.dagger:hilt-compiler:2.38.1" 82 | implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" 83 | kapt "androidx.hilt:hilt-compiler:1.0.0" 84 | 85 | 86 | 87 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/add_friends_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 28 | 29 | 38 | 39 | 55 | 56 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/contacts/ContactsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.contacts 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Bundle 6 | import android.provider.ContactsContract 7 | import androidx.fragment.app.Fragment 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import androidx.core.app.ActivityCompat 12 | import androidx.navigation.fragment.findNavController 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import com.suveybesena.instagramclone.R 15 | import kotlinx.android.synthetic.main.fragment_contacts.* 16 | 17 | 18 | class ContactsFragment : Fragment() { 19 | 20 | lateinit var contactsAdapter: ContactsAdapter 21 | var list = ArrayList() 22 | 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | // Inflate the layout for this fragment 29 | return inflater.inflate(R.layout.fragment_contacts, container, false) 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | 35 | getContactsPermission() 36 | setupRecyclerView() 37 | } 38 | 39 | 40 | private fun getContactsPermission() { 41 | if (ActivityCompat.checkSelfPermission( 42 | this.requireContext(), 43 | Manifest.permission.READ_CONTACTS 44 | ) != PackageManager.PERMISSION_GRANTED 45 | ) { 46 | ActivityCompat.requestPermissions( 47 | this.requireActivity(), 48 | Array(1) { Manifest.permission.READ_CONTACTS }, 49 | 111 50 | ) 51 | } else { 52 | getAllContact() 53 | } 54 | } 55 | 56 | override fun onRequestPermissionsResult( 57 | requestCode: Int, 58 | permissions: Array, 59 | grantResults: IntArray 60 | ) { 61 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 62 | if (requestCode == 111 && grantResults[0] == PackageManager.PERMISSION_GRANTED) getAllContact() 63 | } 64 | 65 | private fun getAllContact() { 66 | val uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI 67 | val cursor = requireActivity().contentResolver?.query(uri, null, null, null, null) 68 | if (cursor != null && cursor.count > 0) { 69 | while (cursor.moveToNext()) { 70 | val name = cursor.getString( 71 | cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME).toInt() 72 | ) 73 | list.add(name) 74 | } 75 | 76 | 77 | } 78 | } 79 | 80 | 81 | private fun setupRecyclerView() { 82 | contactsAdapter = ContactsAdapter(list) 83 | 84 | contactsRecyclerView.apply { 85 | adapter = contactsAdapter 86 | layoutManager = LinearLayoutManager(activity) 87 | } 88 | contactsAdapter.setOnItemClickListener { 89 | val bundle = Bundle().apply { 90 | putString("name", it) 91 | println(it) 92 | } 93 | findNavController().navigate(R.id.action_contactsFragment_to_searchFragment, bundle) 94 | } 95 | 96 | } 97 | 98 | 99 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 23 | 32 | 33 | 41 | 42 | 51 | 52 | 53 | 65 | 66 | 67 | 68 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/profile/ProfileViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.profile 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.google.firebase.firestore.FirebaseFirestore 7 | import com.google.firebase.storage.FirebaseStorage 8 | import com.suveybesena.instagramclone.model.MyPostsModel 9 | import com.suveybesena.instagramclone.di.FirebaseModule 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class ProfileViewModel @Inject 15 | constructor(var firebaseInstance : FirebaseFirestore, 16 | var authInstance : FirebaseAuth 17 | ) 18 | : ViewModel() { 19 | 20 | 21 | private val nameState = MutableLiveData() 22 | val _nameState = nameState 23 | private val imageState = MutableLiveData() 24 | val _imageState = imageState 25 | private val loadingState = MutableLiveData() 26 | val _loadingState = loadingState 27 | private val errorState = MutableLiveData() 28 | val _errorState = errorState 29 | 30 | var tempList = ArrayList() 31 | private val myPostList = MutableLiveData>() 32 | val _myPostList = myPostList 33 | val currentUserId = authInstance.currentUser?.uid.toString() 34 | 35 | fun getDataFromFirebase() { 36 | loadingState.value = true 37 | 38 | firebaseInstance.collection("UsersName").whereEqualTo("uid", currentUserId) 39 | .addSnapshotListener { snapshot, exception -> 40 | if (exception != null) { 41 | loadingState.value = false 42 | errorState.value = exception.localizedMessage 43 | } else { 44 | if (snapshot != null) { 45 | if (!snapshot.isEmpty) { 46 | 47 | val documentList = snapshot.documents 48 | 49 | for (document in documentList) { 50 | 51 | val user = document.get("name") as String 52 | val downloadUri = document.get("image") as String 53 | nameState.value = user 54 | imageState.value = downloadUri 55 | 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | 63 | 64 | firebaseInstance.collection("feedImages").whereEqualTo("uid", currentUserId) 65 | .orderBy("date") 66 | .addSnapshotListener { value, error -> 67 | if (error!=null){ 68 | loadingState.value = false 69 | errorState.value = error.localizedMessage 70 | }else{ 71 | tempList.clear() 72 | if (value != null){ 73 | val documentList = value.documents 74 | for (document in documentList){ 75 | val image = document.get("downloadUri") as String 76 | val uid = document.get("uid") as String 77 | var myPostList = MyPostsModel(image, uid) 78 | tempList.add(myPostList) 79 | } 80 | myPostList.value = tempList 81 | loadingState.value= false 82 | } 83 | } 84 | } 85 | 86 | 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/login/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.login 2 | 3 | import android.os.Bundle 4 | import android.text.Editable 5 | import android.text.TextWatcher 6 | import androidx.fragment.app.Fragment 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.fragment.app.viewModels 11 | import androidx.navigation.Navigation 12 | import com.google.android.material.snackbar.Snackbar 13 | import com.google.firebase.auth.FirebaseAuth 14 | import com.suveybesena.instagramclone.R 15 | import com.suveybesena.instagramclone.databinding.FragmentLoginBinding 16 | import dagger.hilt.android.AndroidEntryPoint 17 | import kotlinx.android.synthetic.main.fragment_login.* 18 | 19 | @AndroidEntryPoint 20 | class LoginFragment : Fragment(), TextWatcher { 21 | 22 | private var mAuth = FirebaseAuth.getInstance() 23 | private val viewModel: LoginViewModel by viewModels() 24 | private lateinit var binding: FragmentLoginBinding 25 | 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | } 30 | 31 | override fun onCreateView( 32 | inflater: LayoutInflater, container: ViewGroup?, 33 | savedInstanceState: Bundle? 34 | ): View { 35 | binding = FragmentLoginBinding.inflate(inflater) 36 | return binding.root 37 | } 38 | 39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 40 | super.onViewCreated(view, savedInstanceState) 41 | 42 | listeners() 43 | 44 | } 45 | 46 | override fun onStart() { 47 | super.onStart() 48 | if (mAuth.currentUser != null) { 49 | view?.let { 50 | Navigation.findNavController(it).navigate(R.id.action_loginFragment_to_homeFragment) 51 | } 52 | } 53 | } 54 | 55 | private fun listeners() { 56 | 57 | binding.btwLogin.isEnabled = false 58 | binding.edtLoginMail.addTextChangedListener(this) 59 | binding.edtLoginPassword.addTextChangedListener(this) 60 | 61 | btw_login.setOnClickListener { 62 | login() 63 | } 64 | tw_sign_up.setOnClickListener { 65 | view?.let { it1 -> 66 | Navigation.findNavController(it1) 67 | .navigate(R.id.action_loginFragment_to_registerFragment) 68 | } 69 | } 70 | } 71 | 72 | fun login() { 73 | val email = binding.edtLoginMail.text.toString() 74 | val password = binding.edtLoginPassword.text.toString() 75 | viewModel.login(email, password) 76 | Navigation.findNavController(requireView()) 77 | .navigate(R.id.action_loginFragment_to_homeFragment) 78 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 79 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 80 | } 81 | 82 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 83 | binding.loginProgressBar.visibility = if (loading == true) View.VISIBLE else View.GONE 84 | } 85 | 86 | } 87 | 88 | override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 89 | } 90 | 91 | override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 92 | } 93 | 94 | override fun afterTextChanged(p0: Editable?) { 95 | binding.btwLogin.isEnabled = 96 | binding.edtLoginMail.text.toString() 97 | .isNotEmpty() && binding.edtLoginPassword.text.toString() 98 | .isNotEmpty() 99 | } 100 | 101 | 102 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/register/RegisterViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.register 2 | 3 | 4 | import android.net.Uri 5 | import android.view.View 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import com.google.firebase.auth.FirebaseAuth 9 | import com.google.firebase.firestore.FirebaseFirestore 10 | import com.google.firebase.storage.FirebaseStorage 11 | import com.suveybesena.instagramclone.di.FirebaseModule 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class RegisterViewModel @Inject 17 | constructor(var firebaseInstance : FirebaseFirestore, 18 | var storageInstance : FirebaseStorage, 19 | var authInstance : FirebaseAuth 20 | ) 21 | : ViewModel() { 22 | 23 | private val loadingState = MutableLiveData() 24 | val _loadingState = loadingState 25 | private val errorState = MutableLiveData() 26 | val _errorState = errorState 27 | private val authState = MutableLiveData() 28 | val _authState = authState 29 | 30 | 31 | 32 | 33 | 34 | 35 | fun register( 36 | mail: String, 37 | password: String, 38 | pickedImage: Uri, 39 | view: View, 40 | username: String, 41 | name: String 42 | ) { 43 | 44 | loadingState.value = true 45 | val user = hashMapOf() 46 | user.put("name", username) 47 | user.put("password", password) 48 | user.put("surname", name) 49 | user.put("bio", "") 50 | user.put("website", "www.test.com") 51 | user.put("image", pickedImage) 52 | 53 | authInstance.createUserWithEmailAndPassword(mail, password).addOnCompleteListener { task -> 54 | if (task.isSuccessful) { 55 | authInstance.currentUser?.let { user.put("uid", it.uid) } 56 | uploadPhoto(pickedImage, username) 57 | view.let { view -> 58 | authState.value = true 59 | } 60 | } 61 | }.addOnFailureListener { exception -> 62 | loadingState.value = false 63 | errorState.value = exception.localizedMessage 64 | } 65 | 66 | } 67 | 68 | fun uploadPhoto(pickedImage: Uri, username: String) { 69 | 70 | val currentUserId = authInstance.currentUser?.uid.toString() 71 | val reference = storageInstance.reference 72 | val imageReference = reference.child("images").child(currentUserId) 73 | 74 | imageReference.putFile(pickedImage).addOnSuccessListener { taskSnapshot -> 75 | 76 | val uploadedImageReference = 77 | FirebaseStorage.getInstance().reference.child("images").child(currentUserId) 78 | uploadedImageReference.downloadUrl.addOnSuccessListener { uri -> 79 | val imageUrl = uri.toString() 80 | val postHashmap = hashMapOf() 81 | postHashmap.put("image", imageUrl) 82 | postHashmap.put("name", username) 83 | postHashmap.put("uid", currentUserId) 84 | 85 | firebaseInstance.collection("UsersName").document(currentUserId).set(postHashmap) 86 | .addOnCompleteListener { task -> 87 | if (task.isSuccessful) { 88 | loadingState.value = false 89 | } 90 | }.addOnFailureListener { exception -> 91 | errorState.value = exception.localizedMessage 92 | 93 | } 94 | } 95 | 96 | }.addOnFailureListener { exception -> 97 | errorState.value = exception.localizedMessage 98 | } 99 | 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/profile/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.profile 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.fragment.app.viewModels 9 | import androidx.navigation.Navigation 10 | import androidx.recyclerview.widget.GridLayoutManager 11 | import com.bumptech.glide.Glide 12 | import com.google.android.material.snackbar.Snackbar 13 | import com.suveybesena.instagramclone.R 14 | import com.suveybesena.instagramclone.databinding.FragmentProfileBinding 15 | import com.suveybesena.instagramclone.model.FollowModel 16 | import com.suveybesena.instagramclone.model.MyPostsModel 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class ProfileFragment : Fragment() { 21 | 22 | lateinit var list: List 23 | private val viewModel: ProfileViewModel by viewModels() 24 | private lateinit var myPostsAdapter: MyPostsAdapter 25 | private lateinit var binding: FragmentProfileBinding 26 | 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | } 31 | 32 | override fun onCreateView( 33 | inflater: LayoutInflater, container: ViewGroup?, 34 | savedInstanceState: Bundle? 35 | ): View? { 36 | binding = FragmentProfileBinding.inflate(inflater) 37 | return binding.root 38 | } 39 | 40 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 41 | super.onViewCreated(view, savedInstanceState) 42 | 43 | viewModel.getDataFromFirebase() 44 | initList() 45 | observeLiveData() 46 | } 47 | 48 | private fun adapter(list: List) { 49 | myPostsAdapter = MyPostsAdapter(list) 50 | binding.myPostsRecycler.apply { 51 | adapter = myPostsAdapter 52 | layoutManager = GridLayoutManager(context, 3) 53 | } 54 | 55 | } 56 | 57 | private fun observeLiveData() { 58 | val userName = binding.twUserName 59 | val imageView = binding.imwProfile 60 | 61 | viewModel._nameState.observe(viewLifecycleOwner) { name -> 62 | userName.setText(name) 63 | } 64 | viewModel._imageState.observe(viewLifecycleOwner) { image -> 65 | Glide.with(this).load(image) 66 | .into(imageView) 67 | } 68 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 69 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 70 | } 71 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 72 | //profile_progress_bar.visibility = if (loading!= null) View.VISIBLE else View.GONE 73 | } 74 | 75 | viewModel._myPostList.observe(viewLifecycleOwner) { list -> 76 | binding.postCount.text = list.size.toString() 77 | adapter(list) 78 | } 79 | 80 | 81 | } 82 | 83 | private fun initList() { 84 | binding.btEditProfile.setOnClickListener { 85 | onclickEditProfile() 86 | } 87 | binding.layoutFollowing.setOnClickListener { 88 | following() 89 | } 90 | binding.layoutFollowers.setOnClickListener { 91 | followers() 92 | } 93 | } 94 | 95 | private fun followers() { 96 | view?.let { 97 | Navigation.findNavController(it) 98 | .navigate(R.id.action_profileFragment_to_followersFragment) 99 | } 100 | } 101 | 102 | private fun following() { 103 | view?.let { 104 | Navigation.findNavController(it) 105 | .navigate(R.id.action_profileFragment_to_followingFragment) 106 | } 107 | } 108 | 109 | private fun onclickEditProfile() { 110 | view?.let { 111 | Navigation.findNavController(it).navigate(R.id.action_profileFragment_to_editFragment) 112 | } 113 | } 114 | 115 | } 116 | 117 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 25 | 26 | 38 | 39 | 57 | 58 | 59 | 60 | 73 | 74 | 75 | 76 | 89 | 90 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /app/src/main/res/layout/feed_recycler_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 21 | 22 | 23 | 31 | 32 | 40 | 41 | 42 | 49 | 56 | 57 | 69 | 70 | 71 | 82 | 83 | 84 | 85 | 86 | 87 | 97 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.home 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.view.* 6 | import android.widget.ImageView 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.Navigation 10 | import com.google.android.material.snackbar.Snackbar 11 | import com.google.firebase.auth.FirebaseAuth 12 | import com.suveybesena.instagramclone.presentation.likes.LikesInterface 13 | import com.suveybesena.instagramclone.R 14 | import com.suveybesena.instagramclone.databinding.FragmentHomeBinding 15 | import com.suveybesena.instagramclone.model.Firebase 16 | import com.suveybesena.instagramclone.presentation.likes.LikesViewModel 17 | import com.suveybesena.instagramclone.presentation.likes.UnlikeInterface 18 | import dagger.hilt.android.AndroidEntryPoint 19 | import kotlinx.android.synthetic.main.dialog_view.* 20 | import kotlinx.android.synthetic.main.feed_recycler_row.* 21 | import kotlinx.android.synthetic.main.top_bar.* 22 | 23 | @AndroidEntryPoint 24 | class HomeFragment : Fragment() { 25 | 26 | private val viewModel: HomeViewModel by viewModels() 27 | private lateinit var feed: FeedAdapter 28 | private val auth= FirebaseAuth.getInstance() 29 | private lateinit var binding : FragmentHomeBinding 30 | 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | } 35 | 36 | override fun onCreateView( 37 | inflater: LayoutInflater, container: ViewGroup?, 38 | savedInstanceState: Bundle? 39 | ): View? { 40 | binding = FragmentHomeBinding.inflate(inflater) 41 | return binding.root 42 | } 43 | 44 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 45 | super.onViewCreated(view, savedInstanceState) 46 | 47 | observeLiveData() 48 | initList() 49 | } 50 | 51 | private fun feedAdapter(userListFirebase: List) { 52 | feed = FeedAdapter(userListFirebase, 53 | object : LikesInterface { 54 | override fun likeItemClick(image: String) { 55 | viewModel.getLikesImage( image) 56 | } 57 | },object : FeedLikesInterface{ 58 | override fun getLikes(image: String, like :ImageView, unlike :ImageView) { 59 | viewModel.checkLikeStatus(image, like, unlike) 60 | viewModel._likeState.observe(viewLifecycleOwner){ likeStatus-> 61 | if (likeStatus == true){ 62 | imw_unLike.visibility = View.VISIBLE 63 | imw_like.visibility = View.INVISIBLE 64 | }else{ 65 | imw_like.visibility = View.VISIBLE 66 | imw_unLike.visibility = View.INVISIBLE 67 | } 68 | 69 | } 70 | } 71 | } , 72 | object :UnlikeInterface{ 73 | override fun unlikeItemClick(image: String) { 74 | viewModel.removeLike(image) 75 | } 76 | 77 | }) 78 | binding.feedRecycler.adapter = feed 79 | 80 | } 81 | 82 | private fun observeLiveData() { 83 | 84 | viewModel.readUserData() 85 | viewModel._firebaseList.observe(viewLifecycleOwner){arrayListFirebase -> 86 | feedAdapter(arrayListFirebase) 87 | } 88 | viewModel._errorState.observe(viewLifecycleOwner){ error-> 89 | if (!error.isNullOrEmpty()) { 90 | Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 91 | } 92 | } 93 | viewModel._loadingState.observe(viewLifecycleOwner){loading -> 94 | binding.homeProgressBar.visibility = if (loading == true) View.VISIBLE else View.GONE 95 | } 96 | } 97 | 98 | private fun initList() { 99 | imw_options.setOnClickListener { 100 | options() 101 | } 102 | } 103 | 104 | private fun options() { 105 | 106 | val dialog = Dialog(requireContext()) 107 | dialog.setContentView(R.layout.dialog_view) 108 | dialog.setCancelable(false) 109 | dialog.dialog_yes.setOnClickListener { 110 | auth.signOut() 111 | Navigation.findNavController(requireView()).navigate(R.id.action_homeFragment_to_loginFragment) 112 | dialog.dismiss() 113 | } 114 | dialog.dialog_no.setOnClickListener { 115 | dialog.dismiss() 116 | } 117 | dialog.show() 118 | 119 | } 120 | 121 | 122 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_share.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 16 | 17 | 23 | 24 | 34 | 35 | 44 | 54 | 55 | 56 | 57 | 58 | 59 | 70 | 73 | 74 | 84 | 85 | 86 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 107 | 108 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/search/SearchFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.search 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Button 9 | import androidx.core.widget.doAfterTextChanged 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.viewModels 12 | import androidx.navigation.Navigation 13 | import androidx.navigation.fragment.navArgs 14 | import com.google.android.material.snackbar.Snackbar 15 | import com.suveybesena.instagramclone.R 16 | import com.suveybesena.instagramclone.databinding.FragmentSearchBinding 17 | import com.suveybesena.instagramclone.model.User 18 | import com.suveybesena.instagramclone.presentation.contacts.ContactsFragment 19 | import dagger.hilt.android.AndroidEntryPoint 20 | import kotlinx.android.synthetic.main.add_friends_item.* 21 | import kotlinx.android.synthetic.main.add_friends_item.view.* 22 | 23 | @AndroidEntryPoint 24 | class SearchFragment : Fragment() { 25 | 26 | private var friendsAdapter: FriendsAdapter? = null 27 | private val viewModel: SearchViewModel by viewModels() 28 | private lateinit var binding: FragmentSearchBinding 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | } 33 | 34 | override fun onCreateView( 35 | inflater: LayoutInflater, container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View? { 38 | binding = FragmentSearchBinding.inflate(inflater) 39 | return binding.root 40 | } 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | 45 | getContacts() 46 | observeLiveData() 47 | initListeners() 48 | } 49 | 50 | private fun feedAdapter(userList: ArrayList) { 51 | friendsAdapter = FriendsAdapter(userList, object : FollowInterface { 52 | override fun itemOnClick(userId: String) { 53 | viewModel.follow(userId) 54 | bw_follow.visibility = View.GONE 55 | bw_unfollow.visibility = View.VISIBLE 56 | } 57 | }, object : UnfollowInterface { 58 | override fun onItemClick(userId: String) { 59 | viewModel.unFollow(userId) 60 | bw_follow.visibility = View.VISIBLE 61 | bw_unfollow.visibility = View.GONE 62 | } 63 | 64 | }, object : GetUserInterface { 65 | override fun getUserList(userId: String, follow: Button, unfollow: Button) { 66 | viewModel.checkFollowingStatus(userId, follow, unfollow) 67 | viewModel._followState.observe(viewLifecycleOwner) { followStatus -> 68 | if (followStatus != true) { 69 | follow.visibility = View.VISIBLE 70 | unfollow.visibility = View.GONE 71 | } else { 72 | unfollow.visibility = View.VISIBLE 73 | follow.visibility = View.GONE 74 | } 75 | } 76 | } 77 | 78 | }) 79 | 80 | binding.searchRecyclerView.adapter = friendsAdapter 81 | } 82 | 83 | private fun initListeners() { 84 | binding.apply { 85 | edtSearch.doAfterTextChanged { text -> 86 | viewModel.retrieveUsers(text.toString()) 87 | binding.searchRecyclerView.visibility = View.VISIBLE 88 | } 89 | iwClose.setOnClickListener { 90 | clearRecycler() 91 | } 92 | 93 | contacts.setOnClickListener { 94 | Navigation.findNavController(it).navigate(R.id.action_searchFragment_to_contactsFragment) 95 | } 96 | } 97 | } 98 | 99 | private fun getContacts(){ 100 | val args = this.arguments 101 | val getContact = args?.get("name").toString() 102 | try { 103 | binding.apply { 104 | viewModel.getContacts(getContact) 105 | binding.searchRecyclerView.visibility = View.VISIBLE 106 | 107 | } 108 | }catch (e: Exception) { 109 | println(e.localizedMessage) 110 | } 111 | 112 | } 113 | 114 | private fun clearRecycler() { 115 | binding.apply { 116 | searchRecyclerView.visibility = View.INVISIBLE 117 | edtSearch.text.clear() 118 | } 119 | 120 | } 121 | 122 | private fun observeLiveData() { 123 | viewModel._userList.observe(viewLifecycleOwner) { arrayListUser -> 124 | arrayListUser?.let { 125 | feedAdapter(arrayListUser) 126 | } 127 | } 128 | viewModel._contactList.observe(viewLifecycleOwner){contactList-> 129 | contactList?.let { 130 | feedAdapter(contactList) 131 | } 132 | } 133 | 134 | 135 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 136 | binding.pbSearchScreen.visibility = if (loading == true) View.VISIBLE else View.GONE 137 | } 138 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 139 | error?.let { errorMessage -> 140 | Snackbar.make(requireView(), errorMessage, Snackbar.LENGTH_LONG).show() 141 | } 142 | } 143 | 144 | } 145 | 146 | 147 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 29 | 30 | 45 | 46 | 47 | 48 | 65 | 66 | 67 | 68 | 69 | 84 | 85 | 86 | 87 | 101 | 102 | 103 | 104 | 119 | 120 | 132 | 133 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/register/RegisterFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.register 2 | 3 | import android.app.Activity 4 | import android.content.ContentResolver 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.graphics.Bitmap 8 | import android.graphics.ImageDecoder 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.provider.MediaStore 13 | import android.view.LayoutInflater 14 | import android.view.View 15 | import android.view.ViewGroup 16 | import androidx.core.app.ActivityCompat 17 | import androidx.core.content.ContextCompat 18 | import androidx.fragment.app.Fragment 19 | import androidx.fragment.app.viewModels 20 | import androidx.navigation.Navigation 21 | import com.google.android.material.snackbar.Snackbar 22 | import com.suveybesena.instagramclone.R 23 | import com.suveybesena.instagramclone.databinding.FragmentRegisterBinding 24 | import dagger.hilt.android.AndroidEntryPoint 25 | import kotlinx.android.synthetic.main.fragment_register.* 26 | 27 | @AndroidEntryPoint 28 | class RegisterFragment : Fragment() { 29 | private val viewModel: RegisterViewModel by viewModels() 30 | private lateinit var contentResolver: ContentResolver 31 | var pickedImage: Uri? = null 32 | var bitmap: Bitmap? = null 33 | 34 | private lateinit var binding: FragmentRegisterBinding 35 | 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | } 40 | 41 | override fun onCreateView( 42 | inflater: LayoutInflater, container: ViewGroup?, 43 | savedInstanceState: Bundle? 44 | ): View? { 45 | binding = FragmentRegisterBinding.inflate(inflater) 46 | return binding.root 47 | 48 | } 49 | 50 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 51 | super.onViewCreated(view, savedInstanceState) 52 | contentResolver = requireActivity().contentResolver 53 | initListeners() 54 | 55 | 56 | } 57 | 58 | private fun initListeners() { 59 | binding.btwRegister.setOnClickListener { 60 | register() 61 | } 62 | binding.imwRegister.setOnClickListener { 63 | imwPickedImage() 64 | } 65 | binding.twSignIn.setOnClickListener { 66 | view?.let { view -> 67 | Navigation.findNavController(view) 68 | .navigate(R.id.action_registerFragment_to_loginFragment) 69 | } 70 | } 71 | } 72 | 73 | 74 | private fun register() { 75 | val register_mail = binding.edtRegisterMail.text.toString() 76 | val register_password = binding.edtRegisterPassword.toString() 77 | val username = binding.edtRegisterUsername.text.toString() 78 | val name = binding.edtRegisterName.text.toString() 79 | pickedImage?.let { image -> 80 | view?.let { view -> 81 | viewModel.register( 82 | register_mail, register_password, 83 | image, view, username, name 84 | ) 85 | } 86 | } 87 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 88 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 89 | } 90 | 91 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 92 | register_progress_bar.visibility = if (loading == true) View.VISIBLE else View.GONE 93 | } 94 | 95 | viewModel._authState.observe(viewLifecycleOwner) { auth -> 96 | if (auth != false) { 97 | view?.let { view -> 98 | Navigation.findNavController(view) 99 | .navigate(R.id.action_registerFragment_to_homeFragment) 100 | } 101 | } 102 | } 103 | 104 | 105 | } 106 | 107 | private fun imwPickedImage() { 108 | if (ContextCompat.checkSelfPermission( 109 | this.requireContext(), 110 | android.Manifest.permission.READ_EXTERNAL_STORAGE 111 | ) != PackageManager.PERMISSION_GRANTED 112 | ) { 113 | ActivityCompat.requestPermissions( 114 | this.requireActivity(), 115 | arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 116 | 1 117 | ) 118 | } else { 119 | val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 120 | startActivityForResult(intent, 2) 121 | } 122 | } 123 | 124 | override fun onRequestPermissionsResult( 125 | requestCode: Int, 126 | permissions: Array, 127 | grantResults: IntArray 128 | ) { 129 | 130 | if (requestCode == 1) { 131 | if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 132 | val intent = 133 | Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 134 | startActivityForResult(intent, 2) 135 | 136 | } 137 | } 138 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 139 | } 140 | 141 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 142 | if (requestCode == 2 && resultCode == Activity.RESULT_OK && data != null) { 143 | 144 | pickedImage = data.data 145 | 146 | if (pickedImage != null) { 147 | 148 | if (Build.VERSION.SDK_INT >= 28) { 149 | val source = ImageDecoder.createSource(this.contentResolver, pickedImage!!) 150 | bitmap = ImageDecoder.decodeBitmap(source) 151 | imw_register.setImageBitmap(bitmap) 152 | 153 | } else { 154 | bitmap = 155 | MediaStore.Images.Media.getBitmap(this.contentResolver, pickedImage) 156 | imw_register.setImageBitmap(bitmap) 157 | } 158 | } 159 | } 160 | super.onActivityResult(requestCode, resultCode, data) 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 25 | 26 | 36 | 37 | 46 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 79 | 80 | 81 | 82 | 83 | 84 | 95 | 96 | 103 | 104 | 108 | 109 | 113 | 114 | 118 | 119 | 123 | 124 | 128 | 129 | 133 | 134 | 138 | 139 | 143 | 144 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 20 | 23 | 24 | 29 | 32 | 38 | 41 | 42 | 47 | /> 48 | 49 | 54 | 57 | 60 | 61 | 66 | 69 | 72 | 73 | 78 | 81 | 84 | 85 | 90 | 93 | 94 | 98 | 101 | 104 | 105 | 106 | 111 | 114 | 115 | 120 | 123 | 124 | 128 | 131 | 132 | 136 | 139 | 140 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/edit/EditFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.edit 2 | 3 | 4 | import android.app.Activity 5 | import android.content.ContentResolver 6 | import android.content.Intent 7 | import android.content.pm.PackageManager 8 | import android.graphics.Bitmap 9 | import android.graphics.ImageDecoder 10 | import android.net.Uri 11 | import android.os.Build 12 | import android.os.Bundle 13 | import android.provider.MediaStore 14 | import androidx.fragment.app.Fragment 15 | import android.view.LayoutInflater 16 | import android.view.View 17 | import android.view.ViewGroup 18 | import androidx.core.app.ActivityCompat 19 | import androidx.core.content.ContextCompat 20 | import androidx.fragment.app.viewModels 21 | import androidx.navigation.Navigation 22 | import com.google.android.material.snackbar.Snackbar 23 | import com.google.firebase.auth.FirebaseAuth 24 | import com.google.firebase.firestore.FirebaseFirestore 25 | import com.google.firebase.storage.FirebaseStorage 26 | import com.suveybesena.instagramclone.R 27 | import com.suveybesena.instagramclone.databinding.FragmentEditBinding 28 | import dagger.hilt.android.AndroidEntryPoint 29 | import kotlinx.android.synthetic.main.fragment_edit.* 30 | 31 | @AndroidEntryPoint 32 | class EditFragment : Fragment() { 33 | private lateinit var storage: FirebaseStorage 34 | private lateinit var auth: FirebaseAuth 35 | private lateinit var database: FirebaseFirestore 36 | 37 | private lateinit var contentResolver: ContentResolver 38 | var pickedImage: Uri? = null 39 | var bitmap: Bitmap? = null 40 | 41 | private lateinit var binding: FragmentEditBinding 42 | 43 | private val viewModel: EditViewModel by viewModels() 44 | 45 | override fun onCreate(savedInstanceState: Bundle?) { 46 | super.onCreate(savedInstanceState) 47 | } 48 | 49 | override fun onCreateView( 50 | inflater: LayoutInflater, container: ViewGroup?, 51 | savedInstanceState: Bundle? 52 | ): View? { 53 | binding = FragmentEditBinding.inflate(inflater) 54 | return binding.root 55 | } 56 | 57 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 58 | super.onViewCreated(view, savedInstanceState) 59 | contentResolver = requireActivity().contentResolver 60 | initList() 61 | getInstance() 62 | } 63 | 64 | private fun getInstance() { 65 | storage = FirebaseStorage.getInstance() 66 | auth = FirebaseAuth.getInstance() 67 | database = FirebaseFirestore.getInstance() 68 | } 69 | 70 | private fun initList() { 71 | 72 | binding.apply { 73 | btwCloseEdit.setOnClickListener { 74 | closeEdit() 75 | } 76 | btwSaveEdit.setOnClickListener { 77 | saveEdit() 78 | } 79 | iwProfile.setOnClickListener { 80 | getProfileImage() 81 | } 82 | } 83 | 84 | } 85 | 86 | private fun saveEdit() { 87 | 88 | val name = binding.edtName.text.toString() 89 | val surname = binding.edtSurname.text.toString() 90 | val bio = binding.edtBio.text.toString() 91 | val webSite = binding.edtWebsite.text.toString() 92 | 93 | pickedImage?.let { viewModel.saveEdit(name, surname, bio, webSite, it) } 94 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 95 | edit_progress_bar.visibility = if (loading == true) View.VISIBLE else View.GONE 96 | } 97 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 98 | if (!error.isNullOrEmpty()) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG) 99 | .show() 100 | } 101 | view?.let { 102 | Navigation.findNavController(it).navigate(R.id.action_editFragment_to_profileFragment) 103 | } 104 | 105 | } 106 | 107 | private fun closeEdit() { 108 | view?.let { 109 | Navigation.findNavController(it).navigate(R.id.action_editFragment_to_profileFragment) 110 | 111 | } 112 | } 113 | 114 | private fun getProfileImage() { 115 | if (ContextCompat.checkSelfPermission( 116 | this.requireContext(), 117 | android.Manifest.permission.READ_EXTERNAL_STORAGE 118 | ) != PackageManager.PERMISSION_GRANTED 119 | ) { 120 | ActivityCompat.requestPermissions( 121 | this.requireActivity(), 122 | arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 123 | 1 124 | ) 125 | } else { 126 | val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 127 | startActivityForResult(intent, 2) 128 | } 129 | } 130 | 131 | override fun onRequestPermissionsResult( 132 | requestCode: Int, 133 | permissions: Array, 134 | grantResults: IntArray 135 | ) { 136 | 137 | if (requestCode == 1) { 138 | if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 139 | val intent = 140 | Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 141 | startActivityForResult(intent, 2) 142 | 143 | } 144 | } 145 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 146 | } 147 | 148 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 149 | if (requestCode == 2 && resultCode == Activity.RESULT_OK && data != null) { 150 | 151 | pickedImage = data.data 152 | 153 | if (pickedImage != null) { 154 | 155 | if (Build.VERSION.SDK_INT >= 28) { 156 | val source = ImageDecoder.createSource(this.contentResolver, pickedImage!!) 157 | bitmap = ImageDecoder.decodeBitmap(source) 158 | binding.iwProfile.setImageBitmap(bitmap) 159 | 160 | } else { 161 | bitmap = 162 | MediaStore.Images.Media.getBitmap(this.contentResolver, pickedImage) 163 | binding.iwProfile.setImageBitmap(bitmap) 164 | } 165 | } 166 | } 167 | super.onActivityResult(requestCode, resultCode, data) 168 | } 169 | 170 | 171 | } -------------------------------------------------------------------------------- /app/src/main/java/com/suveybesena/instagramclone/presentation/share/ShareFragment.kt: -------------------------------------------------------------------------------- 1 | package com.suveybesena.instagramclone.presentation.share 2 | 3 | import android.app.Activity 4 | import android.content.ContentResolver 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.graphics.Bitmap 8 | import android.graphics.ImageDecoder 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.provider.MediaStore 13 | import androidx.fragment.app.Fragment 14 | import android.view.LayoutInflater 15 | import android.view.View 16 | import android.view.ViewGroup 17 | import androidx.core.app.ActivityCompat 18 | import androidx.core.content.ContextCompat 19 | import androidx.fragment.app.viewModels 20 | import androidx.navigation.Navigation 21 | import androidx.navigation.fragment.navArgs 22 | import com.google.android.material.snackbar.Snackbar 23 | import com.suveybesena.instagramclone.R 24 | import com.suveybesena.instagramclone.databinding.FragmentShareBinding 25 | import dagger.hilt.android.AndroidEntryPoint 26 | 27 | @AndroidEntryPoint 28 | class ShareFragment() : Fragment() { 29 | 30 | private lateinit var contentResolver: ContentResolver 31 | private val viewModel: ShareViewModel by viewModels() 32 | private lateinit var binding: FragmentShareBinding 33 | 34 | var pickedImage: Uri? = null 35 | var bitmap: Bitmap? = null 36 | private val args: ShareFragmentArgs by navArgs() 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | } 41 | 42 | override fun onCreateView( 43 | inflater: LayoutInflater, container: ViewGroup?, 44 | savedInstanceState: Bundle? 45 | ): View? { 46 | binding = FragmentShareBinding.inflate(inflater) 47 | return binding.root 48 | } 49 | 50 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 51 | super.onViewCreated(view, savedInstanceState) 52 | contentResolver = requireActivity().contentResolver 53 | 54 | initList() 55 | 56 | } 57 | 58 | private fun savePost() { 59 | if (!args.location.isNullOrEmpty()) { 60 | val location = args.location 61 | val comment = binding.edtComment.toString() 62 | pickedImage?.let { pickedImage -> 63 | if (location != null) { 64 | viewModel.imageDownloader( 65 | pickedImage, 66 | comment, 67 | location 68 | ) 69 | } 70 | 71 | } 72 | Navigation.findNavController(requireView()) 73 | .navigate(R.id.action_shareFragment_to_homeFragment) 74 | } 75 | 76 | viewModel._errorState.observe(viewLifecycleOwner) { error -> 77 | if (error != null) Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show() 78 | } 79 | 80 | viewModel._loadingState.observe(viewLifecycleOwner) { loading -> 81 | binding.shareProgressBar.visibility = if (loading == true) View.VISIBLE else View.GONE 82 | } 83 | 84 | } 85 | 86 | private fun initList() { 87 | if (!args.location.isNullOrEmpty()) { 88 | binding.twGetLocation.text = args.location 89 | } 90 | 91 | binding.apply { 92 | imwShare.setOnClickListener { 93 | imwPickedImage() 94 | } 95 | btwSavePost.setOnClickListener { 96 | savePost() 97 | } 98 | btwClosePost.setOnClickListener { 99 | closePost() 100 | } 101 | twGetLocation.setOnClickListener { 102 | getLocation() 103 | } 104 | } 105 | 106 | } 107 | 108 | private fun getLocation() { 109 | view?.let { Navigation.findNavController(it).navigate(R.id.getUserLocationFragment) } 110 | } 111 | 112 | private fun closePost() { 113 | view?.let { 114 | Navigation.findNavController(it).navigate(R.id.action_shareFragment_to_homeFragment) 115 | } 116 | } 117 | 118 | private fun imwPickedImage() { 119 | if (ContextCompat.checkSelfPermission( 120 | this.requireContext(), 121 | android.Manifest.permission.READ_EXTERNAL_STORAGE 122 | ) != PackageManager.PERMISSION_GRANTED 123 | ) { 124 | ActivityCompat.requestPermissions( 125 | this.requireActivity(), 126 | arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 127 | 1 128 | ) 129 | } else { 130 | val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 131 | startActivityForResult(intent, 2) 132 | } 133 | } 134 | 135 | override fun onRequestPermissionsResult( 136 | requestCode: Int, 137 | permissions: Array, 138 | grantResults: IntArray 139 | ) { 140 | 141 | if (requestCode == 1) { 142 | if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 143 | val intent = 144 | Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 145 | startActivityForResult(intent, 2) 146 | 147 | } 148 | } 149 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 150 | } 151 | 152 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 153 | if (requestCode == 2 && resultCode == Activity.RESULT_OK && data != null) { 154 | 155 | pickedImage = data.data 156 | 157 | if (pickedImage != null) { 158 | 159 | if (Build.VERSION.SDK_INT >= 28) { 160 | val source = ImageDecoder.createSource(this.contentResolver, pickedImage!!) 161 | bitmap = ImageDecoder.decodeBitmap(source) 162 | binding.imwShare.setImageBitmap(bitmap) 163 | 164 | } else { 165 | bitmap = 166 | MediaStore.Images.Media.getBitmap(this.contentResolver, pickedImage) 167 | binding.imwShare.setImageBitmap(bitmap) 168 | } 169 | } 170 | } 171 | super.onActivityResult(requestCode, resultCode, data) 172 | } 173 | } -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------