diff --git a/.gitignore b/.gitignore index 926b2c6..0291895 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,5 @@ /captures .externalNativeBuild .cxx -local.properties /data-core/build/ .kotlin diff --git a/app/src/main/java/com/tymex/github/users/ui/BaseActivity.kt b/app/src/main/java/com/tymex/github/users/ui/BaseActivity.kt new file mode 100644 index 0000000..6d307dc --- /dev/null +++ b/app/src/main/java/com/tymex/github/users/ui/BaseActivity.kt @@ -0,0 +1,31 @@ +package com.tymex.github.users.ui + +import android.os.Bundle +import android.view.Gravity +import androidx.appcompat.app.ActionBar +import androidx.appcompat.app.AppCompatActivity +import com.tymex.github.users.databinding.CustomActionbarTitleBinding + +abstract class BaseActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + supportActionBar?.let { + it.setDisplayHomeAsUpEnabled(true) + it.setDisplayShowTitleEnabled(false) + it.setDisplayShowCustomEnabled(true) + + val titleBinding = CustomActionbarTitleBinding.inflate(layoutInflater) + titleBinding.root.text = getToolbarTitle() + val actionBarParams = ActionBar.LayoutParams( + ActionBar.LayoutParams.MATCH_PARENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL + ) + it.setCustomView(titleBinding.root, actionBarParams) + } + } + + abstract fun getToolbarTitle(): String +} \ No newline at end of file diff --git a/app/src/main/java/com/tymex/github/users/ui/UserAdapter.kt b/app/src/main/java/com/tymex/github/users/ui/UserAdapter.kt index 7938278..7e6cf05 100644 --- a/app/src/main/java/com/tymex/github/users/ui/UserAdapter.kt +++ b/app/src/main/java/com/tymex/github/users/ui/UserAdapter.kt @@ -1,6 +1,5 @@ package com.tymex.github.users.ui -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter @@ -15,7 +14,6 @@ class UserAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val user = currentList[position] - Log.d("tuancoltech", "onBindViewHolder pos: " + position + ". User: " + user.login) (holder as UserHolder).bindItem(user) } @@ -42,21 +40,9 @@ class UserAdapter( fun appendUsers(newUsers: List) { val updatedUsers = currentList + newUsers - Log.w("tuancoltech", "currentList ---- \n") - currentList.printOut() - Log.w("tuancoltech", "newUsers ---- \n") - newUsers.printOut() - Log.w("tuancoltech", "updatedUsers ---- \n") - updatedUsers.printOut() setData(updatedUsers) } - private fun List.printOut() { - for (idx in this.indices) { - Log.v("tuancoltech", "IDX: " + idx + " is " + this[idx].login) - } - } - class UserHolder( private val binding: RowUserBinding, private val onUserClicked: ((User) -> Unit)? = null diff --git a/app/src/main/java/com/tymex/github/users/ui/UserDetailsActivity.kt b/app/src/main/java/com/tymex/github/users/ui/UserDetailsActivity.kt index 654a09f..df3b823 100644 --- a/app/src/main/java/com/tymex/github/users/ui/UserDetailsActivity.kt +++ b/app/src/main/java/com/tymex/github/users/ui/UserDetailsActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.util.Log import android.view.MenuItem import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider @@ -21,9 +20,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @AndroidEntryPoint -class UserDetailsActivity : AppCompatActivity() { - - private val TAG = /*UsersActivity::class.java.simpleName*/"tuancoltech" +class UserDetailsActivity : BaseActivity() { private lateinit var usersViewModel: UserViewModel private lateinit var binding: ActivityUserDetailsBinding @@ -32,10 +29,6 @@ class UserDetailsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivityUserDetailsBinding.inflate(layoutInflater) setContentView(binding.root) - supportActionBar?.let { - it.setDisplayHomeAsUpEnabled(true) - it.title = getString(R.string.toolbar_title_user_details) - } initViewModel() lifecycleScope.launch { @@ -100,4 +93,12 @@ class UserDetailsActivity : AppCompatActivity() { else -> super.onOptionsItemSelected(item) } } + + override fun getToolbarTitle(): String { + return getString(R.string.toolbar_title_user_details) + } + + companion object { + private val TAG by lazy { UsersActivity::class.java.simpleName } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tymex/github/users/ui/UsersActivity.kt b/app/src/main/java/com/tymex/github/users/ui/UsersActivity.kt index 97b737d..2f945c0 100644 --- a/app/src/main/java/com/tymex/github/users/ui/UsersActivity.kt +++ b/app/src/main/java/com/tymex/github/users/ui/UsersActivity.kt @@ -5,7 +5,6 @@ import android.os.Bundle import android.util.Log import android.view.MenuItem import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -16,14 +15,13 @@ import com.tymex.github.data.core.data.model.FlowState import com.tymex.github.data.core.data.model.GetUsersResult import com.tymex.github.data.core.viewmodel.UserViewModel import com.tymex.github.data.core.viewmodel.UserViewModelImpl +import com.tymex.github.users.R import com.tymex.github.users.databinding.ActivityUsersBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @AndroidEntryPoint -class UsersActivity : AppCompatActivity() { - - private val TAG = /*UsersActivity::class.java.simpleName*/"tuancoltech" +class UsersActivity : BaseActivity() { private lateinit var usersViewModel: UserViewModel private lateinit var binding: ActivityUsersBinding @@ -34,8 +32,9 @@ class UsersActivity : AppCompatActivity() { private var isLoadingMore = false companion object { - private const val PAGE_SIZE = 3 + private const val PAGE_SIZE = 20 const val BUNDLE_KEY_LOGIN = "bundle_key_login" + private val TAG by lazy { UsersActivity::class.java.simpleName } } @@ -108,9 +107,9 @@ class UsersActivity : AppCompatActivity() { is FlowState.Success<*> -> { binding.pbLoading.root.gone() val getUserResult = flowState.data as? GetUsersResult ?: return - Log.i( - TAG, - "getUsers success: " + getUserResult.users.size + ". isFinal: " + getUserResult.isFinalPage + ". curPage: " + currentPage + Log.i(TAG, + "getUsers success: " + getUserResult.users.size + + ". isFinal: " + getUserResult.isFinalPage + ". curPage: " + currentPage ) hasMore = !getUserResult.isFinalPage isLoadingMore = false @@ -141,4 +140,8 @@ class UsersActivity : AppCompatActivity() { else -> super.onOptionsItemSelected(item) } } + + override fun getToolbarTitle(): String { + return getString(R.string.app_name) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..075e95d --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_followers.xml b/app/src/main/res/drawable/ic_followers.xml new file mode 100644 index 0000000..488797e --- /dev/null +++ b/app/src/main/res/drawable/ic_followers.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_following.xml b/app/src/main/res/drawable/ic_following.xml new file mode 100644 index 0000000..f5e63e6 --- /dev/null +++ b/app/src/main/res/drawable/ic_following.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_user_details.xml b/app/src/main/res/layout/activity_user_details.xml index d7e111e..eb70f0a 100644 --- a/app/src/main/res/layout/activity_user_details.xml +++ b/app/src/main/res/layout/activity_user_details.xml @@ -20,6 +20,7 @@ android:layout_width="@dimen/small_image_view_size" android:layout_height="@dimen/small_image_view_size" android:layout_marginVertical="@dimen/common_margin" + android:src="@drawable/ic_followers" app:layout_constraintEnd_toStartOf="@+id/iv_following" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/layout_user_card" /> @@ -29,6 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/common_margin" + android:gravity="center_horizontal" app:layout_constraintEnd_toEndOf="@+id/iv_followers" app:layout_constraintStart_toStartOf="@+id/iv_followers" app:layout_constraintTop_toBottomOf="@+id/iv_followers" @@ -39,6 +41,7 @@ android:layout_width="@dimen/small_image_view_size" android:layout_height="@dimen/small_image_view_size" android:layout_marginVertical="@dimen/common_margin" + android:src="@drawable/ic_following" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/iv_followers" app:layout_constraintTop_toBottomOf="@+id/layout_user_card" /> diff --git a/app/src/main/res/layout/custom_actionbar_title.xml b/app/src/main/res/layout/custom_actionbar_title.xml new file mode 100644 index 0000000..cf40e6f --- /dev/null +++ b/app/src/main/res/layout/custom_actionbar_title.xml @@ -0,0 +1,11 @@ + + diff --git a/app/src/main/res/layout/row_user.xml b/app/src/main/res/layout/row_user.xml index 107f26a..127f6a9 100644 --- a/app/src/main/res/layout/row_user.xml +++ b/app/src/main/res/layout/row_user.xml @@ -30,6 +30,7 @@ android:layout_marginStart="20dp" android:ellipsize="end" android:maxLines="1" + android:textColor="@color/black" android:textSize="18sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" @@ -37,17 +38,28 @@ app:layout_constraintTop_toTopOf="@+id/iv_avatar" tools:text="This is a very long text and let's see how long it might take" /> + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7245c97..4eb397f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,8 +7,7 @@ #FF018786 #FF000000 #FFFFFFFF + #ededeb @color/purple_200 - - \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c4f4b34..42652d3 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,5 @@ 20dp - 50dp + 40dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df6b59b..7e7e049 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,8 @@ - GithubUsers + Github Users User Details Error %s Blog - %d \nfollowers - %d \nfollowing + %d \nFollowers + %d \nFollowing \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index df134e3..0cd045f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,16 +1,13 @@ - - + + + \ No newline at end of file diff --git a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/UserRepositoryImpl.kt b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/UserRepositoryImpl.kt index 2eea8a5..64793eb 100644 --- a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/UserRepositoryImpl.kt +++ b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/UserRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.tymex.github.data.core.data.repository.users -import android.util.Log import com.tymex.github.data.core.data.model.FlowState import com.tymex.github.data.core.data.model.GetUsersResult import com.tymex.github.data.core.data.model.User @@ -24,7 +23,6 @@ class UserRepositoryImpl @Inject constructor( when (localUsersFlow) { is FlowState.Success<*> -> { val localUsers = (localUsersFlow.data as? GetUsersResult)?.users ?: emptyList() - Log.v("tuancoltech", "Got localUsers with size: " + localUsers.size) if (localUsers.isNotEmpty()) { flowOf(localUsersFlow) } else { diff --git a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/local/UserLocalDataSourceImpl.kt b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/local/UserLocalDataSourceImpl.kt index e6d6170..cd73ddf 100644 --- a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/local/UserLocalDataSourceImpl.kt +++ b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/local/UserLocalDataSourceImpl.kt @@ -15,14 +15,15 @@ import javax.inject.Inject class UserLocalDataSourceImpl @Inject constructor( private val userDao: UserDao, - @IoScope private val coroutineScope: CoroutineScope + @IoScope private val coroutineScope: CoroutineScope, ) : UserLocalDataSource { + override fun getUsers(pageSize: Int, page: Int): Flow { return flow { emit(FlowState.Loading) val since = (page - 1) * pageSize - Log.i( - "tuancoltech", + Log.d( + TAG, "getUsers page: " + page + ". pageSize: " + pageSize + ". since: " + since ) try { @@ -42,8 +43,12 @@ class UserLocalDataSourceImpl @Inject constructor( override fun insertUsers(users: List) { coroutineScope.launch { - Log.v("tuancoltech", "insertUsers : $users\nSize: $users.size)" ) + Log.v(TAG, "insertUsers : $users\nSize: $users.size)") userDao.insertUsers(users) } } + + companion object { + private val TAG by lazy { UserLocalDataSourceImpl::class.java.name } + } } \ No newline at end of file diff --git a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/remote/UserRemoteDataSourceImpl.kt b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/remote/UserRemoteDataSourceImpl.kt index 73448c5..ab1d4b1 100644 --- a/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/remote/UserRemoteDataSourceImpl.kt +++ b/data-core/src/main/java/com/tymex/github/data/core/data/repository/users/remote/UserRemoteDataSourceImpl.kt @@ -24,7 +24,7 @@ class UserRemoteDataSourceImpl @Inject constructor( var isFinalPage = false val since = (page - 1) * pageSize - Log.i("tuancoltech", "getUsers page: " + page + ". pageSize: " + pageSize + ". since: " + since) + Log.d(TAG, "getUsers page: $page"+ ". pageSize: $pageSize"+ ". since: $since") val networkResponse: NetworkResponse> = try { val response = userService.getUsers(pageSize, since) isFinalPage = response.headers()["Link"]?.isLastPage() ?: false @@ -52,7 +52,7 @@ class UserRemoteDataSourceImpl @Inject constructor( return flow { emit(FlowState.Loading) - Log.i("tuancoltech", "getUserDetails login: " + login) + Log.i(TAG, "getUserDetails login: " + login) val networkResponse: NetworkResponse = try { userService.getUserDetails(login).toNetworkResponse() } catch (ex: Exception) { @@ -70,4 +70,8 @@ class UserRemoteDataSourceImpl @Inject constructor( } }.flowOn(Dispatchers.IO) } + + companion object { + private val TAG by lazy { UserRemoteDataSource::class.java.name } + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66683d4..28407ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,8 +53,3 @@ turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlin-coroutines-test"} [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -android-library = { id = "com.android.library", version.ref = "agp" } -kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin"} - diff --git a/local.properties b/local.properties deleted file mode 100644 index a60e478..0000000 --- a/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -## This file is automatically generated by Android Studio. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file should *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/Users/itabluebelt/Library/Android/sdk \ No newline at end of file