Created
March 17, 2021 23:20
-
-
Save haerulmuttaqin/e5d3c4d7b61707656efc10c49ed2d585 to your computer and use it in GitHub Desktop.
#3 — Job Finder App (https://youtu.be/CINkzqxJtro)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto"> | |
<data> | |
<variable | |
name="viewModel" | |
type="id.haerulmuttaqin.jobfinder.ui.MainViewModel" /> | |
</data> | |
<androidx.coordinatorlayout.widget.CoordinatorLayout | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:background="@color/blue_200"> | |
<com.google.android.material.appbar.AppBarLayout | |
android:id="@+id/appbar" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
app:elevation="0dp"> | |
<com.google.android.material.appbar.CollapsingToolbarLayout | |
android:id="@+id/collapse" | |
android:layout_width="match_parent" | |
android:layout_height="280dp" | |
app:collapsedTitleTextAppearance="@style/collapsedTitleStyle" | |
app:contentScrim="?attr/colorPrimary" | |
app:expandedTitleMarginBottom="26dp" | |
app:expandedTitleMarginStart="26dp" | |
app:expandedTitleTextAppearance="@style/expandedTitleStyle" | |
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" | |
app:title="@string/app_name" | |
app:titleEnabled="true"> | |
<RelativeLayout | |
app:layout_collapseMode="parallax" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<ImageView | |
android:id="@+id/banner" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:scaleType="centerCrop" | |
android:src="@drawable/banner_1" /> | |
<TextView | |
android:id="@+id/welcomeText" | |
android:text="Hi, Welcome back" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="26dp" | |
android:layout_marginTop="40dp" | |
android:layout_marginEnd="10dp" | |
android:fontFamily="@font/book" | |
android:textSize="16sp" | |
android:textColor="@color/white" | |
android:layout_toStartOf="@id/notif"/> | |
<TextView | |
android:id="@+id/secondaryText" | |
android:text="Find your perfect job" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_below="@id/welcomeText" | |
android:layout_marginStart="26dp" | |
android:layout_marginTop="2dp" | |
android:layout_marginEnd="10dp" | |
android:layout_toStartOf="@id/notif" | |
android:textSize="26sp" | |
android:textColor="@color/white" | |
android:fontFamily="@font/bold" /> | |
<ImageView | |
android:id="@+id/notif" | |
android:layout_width="26dp" | |
android:layout_height="40dp" | |
android:layout_alignParentTop="true" | |
android:layout_alignParentEnd="true" | |
android:layout_marginTop="42dp" | |
android:layout_marginEnd="16dp" | |
android:contentDescription="@string/app_name" | |
android:src="@drawable/ic_baseline_more_vert_24" | |
android:paddingBottom="10dp" | |
app:tint="@color/white"/> | |
<LinearLayout | |
android:id="@+id/searchLayout" | |
android:orientation="vertical" | |
android:layout_alignParentBottom="true" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<com.google.android.material.card.MaterialCardView | |
android:id="@+id/card_search" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginLeft="20dp" | |
android:layout_marginRight="20dp" | |
android:layout_marginBottom="60dp" | |
app:strokeWidth="0.3dp" | |
app:cardCornerRadius="20dp" | |
app:cardElevation="0dp" | |
app:strokeColor="@color/blue_300" | |
app:cardBackgroundColor="@color/blue_250"> | |
<RelativeLayout | |
android:layout_width="match_parent" | |
android:layout_height="60dp"> | |
<TextView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:text="What are you looking for?" | |
android:singleLine="true" | |
android:layout_centerVertical="true" | |
android:layout_marginStart="16dp" | |
android:layout_marginEnd="16dp" | |
android:textColor="@color/blue_050" | |
android:fontFamily="@font/book" | |
android:textSize="16dp"/> | |
<ImageView | |
android:src="@drawable/ic_round_search_24" | |
android:layout_width="25dp" | |
android:layout_height="60dp" | |
android:layout_alignParentEnd="true" | |
android:layout_marginEnd="16dp" | |
app:tint="@color/blue_050"/> | |
</RelativeLayout> | |
</com.google.android.material.card.MaterialCardView> | |
</LinearLayout> | |
</RelativeLayout> | |
<androidx.appcompat.widget.Toolbar | |
android:id="@+id/toolbar" | |
android:layout_width="match_parent" | |
android:layout_height="?attr/actionBarSize" | |
android:theme="@style/Theme.JobFinder.Toolbar" | |
android:fitsSystemWindows="true" | |
app:background="@android:color/transparent" | |
app:layout_collapseMode="pin" | |
app:popupTheme="@style/Theme.JobFinder.PopupOverlay" | |
app:titleTextColor="@color/white" /> | |
</com.google.android.material.appbar.CollapsingToolbarLayout> | |
</com.google.android.material.appbar.AppBarLayout> | |
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout | |
android:id="@+id/swipeRefresh" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:background="@drawable/bg_rounded_top" | |
android:fillViewport="true" | |
android:fitsSystemWindows="true" | |
app:layout_anchor="@id/appbar" | |
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> | |
<RelativeLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<androidx.core.widget.NestedScrollView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="16dp" | |
android:fillViewport="true"> | |
<RelativeLayout | |
android:id="@+id/contentLayout" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<TextView | |
android:id="@+id/markedTitle" | |
android:text="Marked Job" | |
android:textSize="16dp" | |
android:textColor="@color/gray_600" | |
android:layout_marginStart="21dp" | |
android:layout_marginEnd="21dp" | |
android:layout_marginTop="25dp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:fontFamily="@font/medium"/> | |
<androidx.recyclerview.widget.RecyclerView | |
android:id="@+id/recyclerViewMarked" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="6dp" | |
android:layout_marginBottom="10dp" | |
android:layout_below="@id/markedTitle" | |
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | |
android:nestedScrollingEnabled="true" | |
android:orientation="horizontal"/> | |
<TextView | |
android:id="@+id/recommendedTitle" | |
android:text="Recommended Job" | |
android:textSize="16dp" | |
android:textColor="@color/gray_600" | |
android:layout_marginStart="21dp" | |
android:layout_marginEnd="21dp" | |
android:layout_marginTop="25dp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_below="@id/recyclerViewMarked" | |
android:fontFamily="@font/medium"/> | |
<androidx.recyclerview.widget.RecyclerView | |
android:id="@+id/recyclerView" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:layout_marginStart="5dp" | |
android:layout_marginEnd="5dp" | |
android:layout_marginBottom="10dp" | |
android:layout_below="@id/recommendedTitle" | |
app:layout_behavior="@string/appbar_scrolling_view_behavior" | |
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> | |
</RelativeLayout> | |
</androidx.core.widget.NestedScrollView> | |
<com.facebook.shimmer.ShimmerFrameLayout | |
android:id="@+id/shimmer" | |
android:visibility="gone" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:orientation="vertical" | |
android:layout_marginTop="16dp" | |
app:layout_behavior="@string/appbar_scrolling_view_behavior" | |
app:shimmer_duration="800" | |
app:shimmer_auto_start="true"> | |
<LinearLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:orientation="vertical"> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
<include layout="@layout/item_job_placeholder" /> | |
</LinearLayout> | |
</com.facebook.shimmer.ShimmerFrameLayout> | |
<LinearLayout | |
android:id="@+id/emptyView" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:orientation="vertical" | |
android:fillViewport="true" | |
android:clickable="false" | |
android:focusable="false" | |
app:layout_behavior="@string/appbar_scrolling_view_behavior" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent"> | |
<ImageView | |
android:layout_width="250dp" | |
android:layout_height="250dp" | |
android:layout_gravity="center" | |
android:layout_marginTop="100dp" | |
android:src="@drawable/ic_undraw_empty" /> | |
<TextView | |
android:id="@+id/textEmpty" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:gravity="center" | |
android:paddingStart="16dp" | |
android:paddingTop="16dp" | |
android:paddingEnd="16dp" | |
android:text="No Data Found" | |
android:fontFamily="@font/medium"/> | |
<TextView | |
android:id="@+id/textEmptyErr" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:gravity="center" | |
android:paddingStart="16dp" | |
android:paddingBottom="16dp" | |
android:paddingEnd="16dp" | |
android:fontFamily="@font/book"/> | |
</LinearLayout> | |
<ImageView | |
android:id="@+id/shadow" | |
android:layout_width="match_parent" | |
android:layout_height="35dp" | |
android:layout_marginTop="16dp" | |
android:src="@drawable/bg_shadow_up_to_down" | |
android:visibility="visible" /> | |
<ImageView | |
android:background="@drawable/bg_strip_round" | |
android:layout_width="40dp" | |
android:layout_height="5dp" | |
android:layout_marginTop="8dp" | |
android:layout_centerHorizontal="true"/> | |
</RelativeLayout> | |
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | |
</androidx.coordinatorlayout.widget.CoordinatorLayout> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.TimeUnit; | |
import javax.inject.Singleton; | |
import dagger.Module; | |
import dagger.Provides; | |
import id.haerulmuttaqin.jobfinder.App; | |
import id.haerulmuttaqin.jobfinder.Constants; | |
import id.haerulmuttaqin.jobfinder.data.api.ApiInterface; | |
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer; | |
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository; | |
import okhttp3.Interceptor; | |
import okhttp3.OkHttpClient; | |
import okhttp3.Request; | |
import okhttp3.logging.HttpLoggingInterceptor; | |
import retrofit2.Retrofit; | |
import retrofit2.converter.gson.GsonConverterFactory; | |
@Module | |
public class AppModule { | |
@Provides @Singleton | |
ConnectionServer provideConnectionServer(ApiInterface apiInterface) { | |
return new ConnectionServer(apiInterface); | |
} | |
@Provides @Singleton | |
Interceptor provideLoggingInterceptor() { | |
HttpLoggingInterceptor localHttpLoggingInterceptor = new HttpLoggingInterceptor(); | |
localHttpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); | |
return localHttpLoggingInterceptor; | |
} | |
@Provides @Singleton | |
OkHttpClient provideOkHttp() { | |
return new OkHttpClient.Builder() | |
.connectTimeout(30, TimeUnit.SECONDS) | |
.writeTimeout(30, TimeUnit.SECONDS) | |
.readTimeout(30, TimeUnit.SECONDS) | |
.addInterceptor(chain -> { | |
Request original = chain.request(); | |
Request.Builder builder = chain.request().newBuilder(); | |
builder.addHeader(Constants.CONTENT_TYPE, Constants.APP_JSON); | |
builder.method(original.method(), original.body()); | |
return chain.proceed(builder.build()); | |
}) | |
.addNetworkInterceptor(provideLoggingInterceptor()) | |
.build(); | |
} | |
@Provides @Singleton | |
Retrofit provideRetrofitClient() { | |
return new Retrofit.Builder().baseUrl(Constants.BASE_URL) | |
.client(provideOkHttp()) | |
.addConverterFactory(GsonConverterFactory.create()) | |
.build(); | |
} | |
@Provides @Singleton | |
ApiInterface provideApiInterface(Retrofit retrofit) { | |
return retrofit.create(ApiInterface.class); | |
} | |
@Provides @Singleton | |
GithubJobRepository provideRepository(App application) { | |
return GithubJobRepository.getInstance(application); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.annotation.NonNull; | |
import androidx.room.Entity; | |
import androidx.room.PrimaryKey; | |
import com.google.gson.annotations.Expose; | |
import com.google.gson.annotations.SerializedName; | |
@Entity | |
public class GithubJob { | |
@PrimaryKey | |
@SerializedName("id") | |
@Expose | |
@NonNull | |
public String id; | |
@SerializedName("type") | |
@Expose | |
public String type; | |
@SerializedName("url") | |
@Expose | |
public String url; | |
@SerializedName("created_at") | |
@Expose | |
public String createdAt; | |
@SerializedName("company") | |
@Expose | |
public String company; | |
@SerializedName("company_url") | |
@Expose | |
public String companyUrl; | |
@SerializedName("location") | |
@Expose | |
public String location; | |
@SerializedName("title") | |
@Expose | |
public String title; | |
@SerializedName("description") | |
@Expose | |
public String description; | |
@SerializedName("how_to_apply") | |
@Expose | |
public String howToApply; | |
@SerializedName("company_logo") | |
@Expose | |
public String companyLogo; | |
public int is_mark; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.lifecycle.LiveData; | |
import androidx.room.Dao; | |
import androidx.room.Insert; | |
import androidx.room.OnConflictStrategy; | |
import androidx.room.Query; | |
import java.util.List; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
@Dao | |
public interface GithubJobDao { | |
@Insert(onConflict = OnConflictStrategy.REPLACE) | |
void insert(GithubJob githubJob); | |
@Query("SELECT * FROM githubjob ORDER BY createdAt DESC LIMIT 10") | |
LiveData<List<GithubJob>> getLiveData(); | |
@Query("SELECT * FROM githubjob where is_mark = 1 ORDER BY createdAt DESC") | |
LiveData<List<GithubJob>> getLiveDataMarked(); | |
@Query("SELECT * FROM githubjob where id = :id") | |
GithubJob getDataById(String id); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import androidx.room.Database; | |
import androidx.room.Room; | |
import androidx.room.RoomDatabase; | |
import id.haerulmuttaqin.jobfinder.Constants; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
@Database(entities = GithubJob.class, version = 1) | |
public abstract class GithubJobDatabase extends RoomDatabase { | |
public abstract GithubJobDao githubJobDao(); | |
public static Context context; | |
private static GithubJobDatabase instance; | |
public static GithubJobDatabase getDatabase(Context ctx) { | |
context = ctx; | |
if (instance == null) { | |
synchronized (GithubJobDatabase.class) { | |
if (instance == null) { | |
instance = Room.databaseBuilder(context, GithubJobDatabase.class, Constants.MASTER_DB) | |
.allowMainThreadQueries() | |
.setJournalMode(JournalMode.TRUNCATE) | |
.build(); | |
} | |
} | |
} | |
return instance; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import androidx.lifecycle.LiveData; | |
import java.util.List; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
public class GithubJobRepository { | |
private static GithubJobDatabase database; | |
private static GithubJobRepository repository; | |
public GithubJobRepository(Context context) { | |
database = GithubJobDatabase.getDatabase(context); | |
} | |
public static GithubJobRepository getInstance(Context context) { | |
if (repository == null) { | |
repository = new GithubJobRepository(context); | |
} | |
return repository; | |
} | |
public void updateMarkJob(GithubJob githubJob) { | |
githubJob.is_mark = githubJob.is_mark == 1 ? 0 : 1; | |
database.githubJobDao().insert(githubJob); | |
} | |
public void insert(GithubJob githubJob) { | |
if (database.githubJobDao().getDataById(githubJob.id) == null) { | |
database.githubJobDao().insert(githubJob); | |
} | |
} | |
public LiveData<List<GithubJob>> getLiveData() { | |
return database.githubJobDao().getLiveData(); | |
} | |
public LiveData<List<GithubJob>> getLiveDataMarked() { | |
return database.githubJobDao().getLiveDataMarked(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<layout xmlns:tools="http://schemas.android.com/tools" | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto"> | |
<data> | |
<import type="android.view.View" /> | |
<variable | |
name="item" | |
type="id.haerulmuttaqin.jobfinder.data.entity.GithubJob" /> | |
<variable | |
name="viewModel" | |
type="id.haerulmuttaqin.jobfinder.ui.MainViewModel" /> | |
</data> | |
<com.google.android.material.card.MaterialCardView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="10dp" | |
android:layout_marginEnd="10dp" | |
android:layout_marginTop="8dp" | |
app:cardCornerRadius="10dp" | |
app:cardElevation="0dp" | |
app:strokeColor="#f1f1f1" | |
app:strokeWidth="1dp"> | |
<LinearLayout | |
android:orientation="vertical" | |
android:clickable="true" | |
android:focusable="true" | |
android:background="?attr/selectableItemBackground" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<RelativeLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="9dp" | |
android:padding="4dp"> | |
<com.google.android.material.card.MaterialCardView | |
android:id="@+id/photoPreviewLayout" | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:layout_marginStart="6dp" | |
android:layout_marginTop="6dp" | |
android:src="@drawable/ic_photo" | |
app:strokeWidth="0.5dp" | |
app:strokeColor="#f1f1f1" | |
app:cardElevation="0dp" | |
app:cardCornerRadius="8dp"> | |
<ImageView | |
android:id="@+id/photoPreview" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:scaleType="centerInside" | |
android:layout_margin="5dp" | |
tools:ignore="UnusedAttribute" /> | |
<com.facebook.shimmer.ShimmerFrameLayout | |
android:id="@+id/progress" | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:orientation="vertical" | |
app:shimmer_auto_start="true" | |
app:shimmer_duration="800"> | |
<View | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:gravity="center" | |
android:layout_gravity="center" | |
android:background="@color/gray_100" /> | |
</com.facebook.shimmer.ShimmerFrameLayout> | |
</com.google.android.material.card.MaterialCardView> | |
<LinearLayout | |
android:layout_toEndOf="@id/photoPreviewLayout" | |
android:orientation="vertical" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="6dp"> | |
<TextView | |
android:id="@+id/title" | |
android:ellipsize="end" | |
android:maxLines="2" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="16dp" | |
android:layout_marginEnd="36dp" | |
android:text="@{item.title}" | |
android:textColor="@color/gray_500" | |
android:textSize="18sp" | |
android:layout_marginBottom="3dp" | |
android:fontFamily="@font/bold"/> | |
<RelativeLayout | |
android:layout_marginStart="14dp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<ImageView | |
android:id="@+id/lampIcon" | |
android:src="@drawable/ic_round_business_24" | |
app:tint="@color/gray_300" | |
android:layout_marginEnd="5dp" | |
android:layout_width="15dp" | |
android:layout_height="16sp"/> | |
<TextView | |
android:layout_toEndOf="@id/lampIcon" | |
android:id="@+id/lamp" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textColor="@color/gray_400" | |
android:text="@{item.company}" | |
android:textSize="12sp" | |
android:singleLine="true" | |
android:ellipsize="end" | |
android:fontFamily="@font/book"/> | |
</RelativeLayout> | |
<RelativeLayout | |
android:layout_marginStart="14dp" | |
android:layout_marginTop="3dp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<ImageView | |
android:id="@+id/locIcon" | |
android:src="@drawable/ic_outline_location_on_24" | |
app:tint="@color/gray_300" | |
android:layout_marginEnd="5dp" | |
android:layout_width="15dp" | |
android:layout_height="16sp"/> | |
<TextView | |
android:layout_toEndOf="@id/locIcon" | |
android:id="@+id/address" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textColor="@color/gray_400" | |
android:text="@{item.location}" | |
android:textSize="12sp" | |
android:singleLine="true" | |
android:ellipsize="end" | |
android:fontFamily="@font/book"/> | |
</RelativeLayout> | |
<RelativeLayout | |
android:layout_marginStart="14dp" | |
android:layout_marginTop="3dp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="6dp" | |
android:paddingStart="6dp" | |
android:paddingEnd="6dp" | |
android:paddingTop="2dp" | |
android:paddingBottom="2dp" | |
android:background="@drawable/bg_job_type" | |
android:textColor="@color/green_300" | |
android:text="@{item.type}" | |
android:textSize="10sp" | |
android:fontFamily="@font/book"/> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="16dp" | |
android:layout_marginTop="6dp" | |
android:paddingStart="6dp" | |
android:paddingEnd="6dp" | |
android:paddingTop="2dp" | |
android:paddingBottom="2dp" | |
android:layout_alignParentEnd="true" | |
android:textColor="@color/gray_300" | |
android:text="@{viewModel.formatDate(item.createdAt)}" | |
android:textSize="11sp" | |
android:fontFamily="@font/book"/> | |
</RelativeLayout> | |
</LinearLayout> | |
<ImageView | |
android:onClick="@{()->viewModel.markJob(item)}" | |
android:clickable="true" | |
android:focusable="true" | |
android:background="?attr/selectableItemBackgroundBorderless" | |
android:id="@+id/mark" | |
android:layout_width="30dp" | |
android:layout_height="40dp" | |
android:src="@{item.is_mark == 1 ? @drawable/ic_marked : @drawable/ic_mark}" | |
android:layout_alignParentEnd="true" | |
android:padding="3dp" | |
app:tint="@color/gray_200"/> | |
</RelativeLayout> | |
</LinearLayout> | |
</com.google.android.material.card.MaterialCardView> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<layout xmlns:tools="http://schemas.android.com/tools" | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto"> | |
<data> | |
<import type="android.view.View" /> | |
<variable | |
name="item" | |
type="id.haerulmuttaqin.jobfinder.data.entity.GithubJob" /> | |
<variable | |
name="viewModel" | |
type="id.haerulmuttaqin.jobfinder.ui.MainViewModel" /> | |
</data> | |
<com.google.android.material.card.MaterialCardView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="14dp" | |
android:layout_marginTop="8dp" | |
app:cardCornerRadius="10dp" | |
app:cardElevation="0dp" | |
app:strokeColor="#f1f1f1" | |
app:strokeWidth="1dp"> | |
<LinearLayout | |
android:orientation="vertical" | |
android:clickable="true" | |
android:focusable="true" | |
android:background="?attr/selectableItemBackground" | |
android:layout_width="200dp" | |
android:layout_height="wrap_content"> | |
<RelativeLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="9dp" | |
android:padding="4dp"> | |
<com.google.android.material.card.MaterialCardView | |
android:id="@+id/photoPreviewLayout" | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:layout_marginStart="14dp" | |
android:layout_marginTop="6dp" | |
android:src="@drawable/ic_photo" | |
app:strokeWidth="0.5dp" | |
app:strokeColor="#f1f1f1" | |
app:cardElevation="0dp" | |
app:cardCornerRadius="8dp"> | |
<ImageView | |
android:id="@+id/photoPreview" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:scaleType="centerInside" | |
android:layout_margin="5dp" | |
tools:ignore="UnusedAttribute" /> | |
<com.facebook.shimmer.ShimmerFrameLayout | |
android:id="@+id/progress" | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:orientation="vertical" | |
app:shimmer_auto_start="true" | |
app:shimmer_duration="800"> | |
<View | |
android:layout_width="60dp" | |
android:layout_height="60dp" | |
android:gravity="center" | |
android:layout_gravity="center" | |
android:background="@color/gray_100" /> | |
</com.facebook.shimmer.ShimmerFrameLayout> | |
</com.google.android.material.card.MaterialCardView> | |
<LinearLayout | |
android:layout_below="@id/photoPreviewLayout" | |
android:orientation="vertical" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="6dp"> | |
<TextView | |
android:id="@+id/title" | |
android:ellipsize="end" | |
android:maxLines="2" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="16dp" | |
android:layout_marginEnd="16dp" | |
android:text="@{item.title}" | |
android:textColor="@color/gray_500" | |
android:textSize="18sp" | |
android:layout_marginBottom="3dp" | |
android:fontFamily="@font/bold"/> | |
<TextView | |
android:id="@+id/lamp" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="16dp" | |
android:layout_marginEnd="16dp" | |
android:textColor="@color/gray_400" | |
android:text="@{item.company}" | |
android:textSize="12sp" | |
android:singleLine="true" | |
android:ellipsize="end" | |
android:fontFamily="@font/book"/> | |
</LinearLayout> | |
<ImageView | |
android:onClick="@{()->viewModel.markJob(item)}" | |
android:clickable="true" | |
android:focusable="true" | |
android:background="?attr/selectableItemBackgroundBorderless" | |
android:id="@+id/mark" | |
android:layout_width="30dp" | |
android:layout_height="40dp" | |
android:src="@{item.is_mark == 1 ? @drawable/ic_marked : @drawable/ic_mark}" | |
android:layout_alignParentEnd="true" | |
android:padding="3dp" | |
app:tint="@color/gray_200"/> | |
</RelativeLayout> | |
</LinearLayout> | |
</com.google.android.material.card.MaterialCardView> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Bundle; | |
import android.view.View; | |
import androidx.annotation.Nullable; | |
import androidx.lifecycle.ViewModelProvider; | |
import com.google.android.material.snackbar.Snackbar; | |
import javax.inject.Inject; | |
import id.haerulmuttaqin.jobfinder.R; | |
import id.haerulmuttaqin.jobfinder.base.BaseActivity; | |
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer; | |
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository; | |
import id.haerulmuttaqin.jobfinder.databinding.ActivityMainBinding; | |
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> implements MainViewModel.Navigator { | |
@Inject ConnectionServer server; | |
@Inject GithubJobRepository repository; | |
private ActivityMainBinding binding; | |
private MainViewModel viewModel; | |
@Override | |
public int getBindingVariable() { | |
return 0; | |
} | |
@Override | |
public int getLayoutId() { | |
return R.layout.activity_main; | |
} | |
@Override | |
public MainViewModel getViewModel() { | |
return viewModel; | |
} | |
@Override | |
protected void onCreate(@Nullable Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
binding = getViewDataBinding(); | |
viewModel = new ViewModelProvider(this, new MainViewModel.ModelFactory(this, server, repository)).get(MainViewModel.class); | |
viewModel.setNavigator(this); | |
viewModel.getJobFromServer(); | |
viewModel.getLiveData().observe(this, githubJobs -> { | |
binding.recyclerView.setAdapter(new MainAdapter(githubJobs, viewModel)); | |
}); | |
viewModel.getLiveDataMarked().observe(this, githubJobs -> { | |
if (githubJobs.size() > 0) { | |
binding.markedTitle.setVisibility(View.VISIBLE); | |
binding.recyclerViewMarked.setVisibility(View.VISIBLE); | |
binding.recyclerViewMarked.setAdapter(new MainMarkedAdapter(githubJobs, viewModel)); | |
} else { | |
binding.markedTitle.setVisibility(View.GONE); | |
binding.recyclerViewMarked.setVisibility(View.GONE); | |
} | |
}); | |
binding.swipeRefresh.setOnRefreshListener(()->viewModel.getJobFromServer()); | |
} | |
@Override | |
public void showProgress() { | |
binding.swipeRefresh.setRefreshing(true); | |
binding.emptyView.setVisibility(View.GONE); | |
binding.shimmer.setVisibility(View.VISIBLE); | |
binding.shimmer.startShimmer(); | |
binding.contentLayout.setVisibility(View.GONE); | |
} | |
@Override | |
public void hideProgress() { | |
binding.swipeRefresh.setRefreshing(false); | |
binding.emptyView.setVisibility(View.GONE); | |
binding.shimmer.setVisibility(View.GONE); | |
binding.shimmer.stopShimmer(); | |
binding.contentLayout.setVisibility(View.VISIBLE); | |
} | |
@Override | |
public void onGetResult(boolean status, String message) { | |
if (!status) { //<-- status result is FALSE | |
binding.textEmptyErr.setText(message); | |
binding.emptyView.setVisibility(View.VISIBLE); | |
} else { | |
binding.emptyView.setVisibility(View.GONE); | |
} | |
} | |
@Override | |
public void onMark(int mark, String title) { | |
Snackbar.make(binding.getRoot(), mark == 0 ? "\uD83D\uDE13 Unmark " + title : "\uD83D\uDE0D Marked " + title, Snackbar.LENGTH_SHORT).show(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.graphics.drawable.Drawable; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import androidx.annotation.NonNull; | |
import androidx.annotation.Nullable; | |
import androidx.databinding.DataBindingUtil; | |
import androidx.recyclerview.widget.RecyclerView; | |
import com.bumptech.glide.Glide; | |
import com.bumptech.glide.load.DataSource; | |
import com.bumptech.glide.load.engine.GlideException; | |
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; | |
import com.bumptech.glide.request.RequestListener; | |
import com.bumptech.glide.request.target.Target; | |
import java.util.List; | |
import id.haerulmuttaqin.jobfinder.R; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
import id.haerulmuttaqin.jobfinder.databinding.ItemJobBinding; | |
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.RecyclerViewAdapter> { | |
private List<GithubJob> data; | |
private MainViewModel viewModel; | |
protected MainAdapter(List<GithubJob> data, MainViewModel viewModel) { | |
this.data = data; | |
this.viewModel = viewModel; | |
} | |
@NonNull | |
@Override | |
public RecyclerViewAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | |
LayoutInflater inflater = LayoutInflater.from(parent.getContext()); | |
ItemJobBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_job, parent, false); | |
return new RecyclerViewAdapter(binding); | |
} | |
public void clear() { | |
int size = data.size(); | |
data.clear(); | |
notifyItemRangeRemoved(0, size); | |
notifyDataSetChanged(); | |
} | |
@Override | |
public void onBindViewHolder(RecyclerViewAdapter holder, int position) { | |
GithubJob items = data.get(position); | |
if(items != null) { | |
holder.bind(items, viewModel); | |
} | |
} | |
@Override | |
public int getItemCount() { | |
return data == null ? 0 : data.size(); | |
} | |
public static class RecyclerViewAdapter extends RecyclerView.ViewHolder { | |
ItemJobBinding binding; | |
public RecyclerViewAdapter(ItemJobBinding binding) { | |
super(binding.getRoot()); | |
this.binding = binding; | |
} | |
@SuppressLint("UseCompatLoadingForDrawables") | |
void bind(@NonNull GithubJob data, MainViewModel viewModel) { | |
Glide.with(binding.photoPreview.getContext()) | |
.load(data.companyLogo) | |
.error(binding.photoPreview.getContext().getDrawable(R.drawable.ic_round_business_center_24)) | |
.transition(DrawableTransitionOptions.withCrossFade()) | |
.listener(new RequestListener<Drawable>() { | |
@Override | |
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { | |
binding.progress.stopShimmer(); | |
binding.progress.setVisibility(View.GONE); | |
return false; | |
} | |
@Override | |
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { | |
binding.progress.stopShimmer(); | |
binding.progress.setVisibility(View.GONE); | |
return false; | |
} | |
}) | |
.into(binding.photoPreview); | |
binding.setItem(data); | |
binding.setViewModel(viewModel); | |
binding.executePendingBindings(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint; | |
import android.graphics.drawable.Drawable; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import androidx.annotation.NonNull; | |
import androidx.annotation.Nullable; | |
import androidx.databinding.DataBindingUtil; | |
import androidx.recyclerview.widget.RecyclerView; | |
import com.bumptech.glide.Glide; | |
import com.bumptech.glide.load.DataSource; | |
import com.bumptech.glide.load.engine.GlideException; | |
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; | |
import com.bumptech.glide.request.RequestListener; | |
import com.bumptech.glide.request.target.Target; | |
import java.util.List; | |
import id.haerulmuttaqin.jobfinder.R; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
import id.haerulmuttaqin.jobfinder.databinding.ItemJobMarkedBinding; | |
public class MainMarkedAdapter extends RecyclerView.Adapter<MainMarkedAdapter.RecyclerViewAdapter> { | |
private List<GithubJob> data; | |
private MainViewModel viewModel; | |
protected MainMarkedAdapter(List<GithubJob> data, MainViewModel viewModel) { | |
this.data = data; | |
this.viewModel = viewModel; | |
} | |
@NonNull | |
@Override | |
public RecyclerViewAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | |
LayoutInflater inflater = LayoutInflater.from(parent.getContext()); | |
ItemJobMarkedBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_job_marked, parent, false); | |
return new RecyclerViewAdapter(binding); | |
} | |
public void clear() { | |
int size = data.size(); | |
data.clear(); | |
notifyItemRangeRemoved(0, size); | |
notifyDataSetChanged(); | |
} | |
@Override | |
public void onBindViewHolder(RecyclerViewAdapter holder, int position) { | |
GithubJob items = data.get(position); | |
if(items != null) { | |
holder.bind(items, viewModel); | |
} | |
} | |
@Override | |
public int getItemCount() { | |
return data == null ? 0 : data.size(); | |
} | |
public static class RecyclerViewAdapter extends RecyclerView.ViewHolder { | |
ItemJobMarkedBinding binding; | |
public RecyclerViewAdapter(ItemJobMarkedBinding binding) { | |
super(binding.getRoot()); | |
this.binding = binding; | |
} | |
@SuppressLint("UseCompatLoadingForDrawables") | |
void bind(@NonNull GithubJob data, MainViewModel viewModel) { | |
Glide.with(binding.photoPreview.getContext()) | |
.load(data.companyLogo) | |
.error(binding.photoPreview.getContext().getDrawable(R.drawable.ic_round_business_center_24)) | |
.transition(DrawableTransitionOptions.withCrossFade()) | |
.listener(new RequestListener<Drawable>() { | |
@Override | |
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { | |
binding.progress.stopShimmer(); | |
binding.progress.setVisibility(View.GONE); | |
return false; | |
} | |
@Override | |
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { | |
binding.progress.stopShimmer(); | |
binding.progress.setVisibility(View.GONE); | |
return false; | |
} | |
}) | |
.into(binding.photoPreview); | |
binding.setItem(data); | |
binding.setViewModel(viewModel); | |
binding.executePendingBindings(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import androidx.annotation.NonNull; | |
import androidx.lifecycle.LiveData; | |
import androidx.lifecycle.MutableLiveData; | |
import androidx.lifecycle.ViewModel; | |
import androidx.lifecycle.ViewModelProvider; | |
import java.util.List; | |
import id.haerulmuttaqin.jobfinder.Utils; | |
import id.haerulmuttaqin.jobfinder.base.BaseViewModel; | |
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer; | |
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob; | |
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository; | |
import retrofit2.Call; | |
import retrofit2.Callback; | |
import retrofit2.Response; | |
public class MainViewModel extends BaseViewModel<MainViewModel.Navigator> { | |
MutableLiveData<List<GithubJob>> jobList = new MutableLiveData<>(); | |
public MainViewModel(Context context, ConnectionServer connectionServer, GithubJobRepository repository) { | |
super(context, connectionServer, repository); | |
} | |
public LiveData<List<GithubJob>> getLiveData() { | |
return getRepository().getLiveData(); | |
} | |
public LiveData<List<GithubJob>> getLiveDataMarked() { | |
return getRepository().getLiveDataMarked(); | |
} | |
public String formatDate(String date){ | |
return Utils.dateToTimeFormat(date); | |
} | |
public void getJobFromServer() { | |
getNavigator().showProgress(); | |
getConnectionServer().getJobList().enqueue(new Callback<List<GithubJob>>() { | |
@Override | |
public void onResponse(Call<List<GithubJob>> call, Response<List<GithubJob>> response) { | |
if (response.isSuccessful() && response.body().size() > 0) { | |
for (GithubJob item : response.body()) { | |
item.createdAt = Utils.dateFormatter(item.createdAt); // format date | |
getRepository().insert(item); | |
} | |
getNavigator().onGetResult(true, "Success"); | |
} | |
getNavigator().hideProgress(); | |
} | |
@Override | |
public void onFailure(Call<List<GithubJob>> call, Throwable t) { | |
t.getLocalizedMessage(); | |
getNavigator().hideProgress(); | |
getNavigator().onGetResult(false, Utils.errorMessageHandler(call, t)); | |
} | |
}); | |
} | |
public void markJob(GithubJob githubJob) { | |
getRepository().updateMarkJob(githubJob); | |
getNavigator().onMark(githubJob.is_mark, githubJob.title); | |
} | |
public static class ModelFactory implements ViewModelProvider.Factory { | |
private Context context; | |
private ConnectionServer server; | |
private GithubJobRepository repository; | |
public ModelFactory(Context context, ConnectionServer server, GithubJobRepository repository) { | |
this.context = context; | |
this.server = server; | |
this.repository = repository; | |
} | |
@NonNull | |
@Override | |
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { | |
return (T) new MainViewModel(context, server, repository); | |
} | |
} | |
interface Navigator { | |
void showProgress(); | |
void hideProgress(); | |
void onGetResult(boolean status, String message); | |
void onMark(int mark, String title); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment