18,19, 20일차 | 블로그 포스팅
Windows + Alt + R을 눌러서 화면 녹화
코드 추가
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.blog"
minSdk 25
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures{
viewBinding{
enabled true
}
}
}
def retrofit_version = "2.9.0"
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//레트로핏
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
//글라이드
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
//circle 이미지 만들 때
implementation 'de.hdodenhof:circleimageview:3.1.0'
}
splash 화면구현
res>anim>rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="infinite"
android:duration="500"
android:pivotX="100%"
android:pivotY="100%"
android:fromDegrees="0"
android:toDegrees="360">
</rotate>
res>layout>activity_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="#071250"
android:layout_height="match_parent"
tools:context=".SplashActivity">
<ImageView
android:id="@+id/splashIv"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:src="@drawable/ic_android_black_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
SplashActivity.java
package com.example.blog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
ImageView imageView = findViewById(R.id.splashIv);
Animation slideAnimation = AnimationUtils.loadAnimation(this, R.anim.rotate);
imageView.startAnimation(slideAnimation);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
}
}, 2000);
}
}
adapter>BlogListAdapter.java
package com.example.blog.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.blog.R;
import com.example.blog.interfaces.OnPageRequest;
import com.example.blog.models.response.Data;
import java.util.ArrayList;
import java.util.List;
public class BlogListAdapter extends RecyclerView.Adapter<BlogListAdapter.ViewHolder>{
private List<Data> list = new ArrayList<>();
private OnPageRequest onPageRequest;
//생성자, 메서드로 주소 받기
public BlogListAdapter(OnPageRequest onPageRequest){
this.onPageRequest = onPageRequest;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.item_blog_card, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Data data = list.get(position);
//이미지 그림을 그려야 한다.
holder.titleView.setText(data.title);
holder.usernameView.setText(data.user.getUsername());
holder.contentView.setText(data.content);
Glide.with(holder.imageView.getContext())
.load("https://picsum.photos/200" + position)
.centerCrop()
.into(holder.imageView);
holder.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//옵저버패턴
onPageRequest.onPageChange(data.id);
}
});
}
@Override
public int getItemCount() {
return list.size();
}
@SuppressLint("notifyDataSetChanged")
public void refreshItems(List<Data> dataList){
this.list = dataList;
notifyDataSetChanged();
}
@SuppressLint("NotifyDataSetChanged")
public void addItems(List<Data> dataList){
this.list.addAll(this.list.size(), dataList);
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder{
View itemView;
ImageView imageView;
TextView titleView;
TextView usernameView;
TextView contentView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.itemView = itemView;
imageView = itemView.findViewById(R.id.blogImageView);
titleView = itemView.findViewById(R.id.blogTitleTextView);
usernameView = itemView.findViewById(R.id.blogUserTextView);
contentView = itemView.findViewById(R.id.blogContentTextView);
}
}
}
interfaces>OnPageRequest
package com.example.blog.interfaces;
public interface OnPageRequest {
void onPageChange(int postId);
}
interfaces>OnRefreshFragment
package com.example.blog.interfaces;
public interface OnRefreshFragment {
void refresh();
}
//자식요소에서 부모 요소에게 : 옵저버
//부모요소에서 자식 요소에게 : public
models>common>Response
package com.example.blog.models.common;
public class Response {
public int code;
public String msg;
}
models>request>ReqJoin
package com.example.blog.models.request;
public class ReqJoin {
public String username;
public String password;
public String email;
public ReqJoin(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
}
models>request>ReqLogin
package com.example.blog.models.request;
public class ReqLogin {
public String username;
public String password;
public ReqLogin(String username, String password) {
this.username = username;
this.password = password;
}
}
models>request>ReqPost
package com.example.blog.models.request;
public class ReqPost {
public String title;
public String content;
public ReqPost(String title, String content) {
this.title = title;
this.content = content;
}
}
models>response>Data
package com.example.blog.models.response;
public class Data {
public int id;
public String username;
public String email;
public String created;
public String updated;
public String title;
public String content;
public ResUserDto user;
public String password;
@Override
public String toString() {
return "Data{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", created='" + created + '\'' +
", updated='" + updated + '\'' +
", title='" + title + '\'' +
", content='" + content + '\'' +
", user=" + user +
", password='" + password + '\'' +
'}';
}
}
models>response>ResDelete
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
public class ResDelete extends Response {
public Data data;
}
models>response>ResJoin
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
public class ResJoin extends Response {
public Data data;
@Override
public String toString() {
return "ResJoin{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
models>response>ResLogin
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
public class ResLogin extends Response {
public Data data;
@Override
public String toString() {
return "ResLogin{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
models>response>ResPost
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
import java.util.List;
public class ResPost extends Response {
public List<Data> data;
@Override
public String toString() {
return "ResPost{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
models>response>ResPostById
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
public class ResPostById extends Response {
public Data data;
@Override
public String toString() {
return "ResPostById{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
models>response>ResUserDto
package com.example.blog.models.response;
import com.example.blog.models.common.Response;
public class ResUserDto extends Response {
private int id;
private String username;
private String password;
private String email;
private String created;
private String updated;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCreated() {
return created;
}
public void setCreated(String created) {
this.created = created;
}
public String getUpdated() {
return updated;
}
public void setUpdated(String updated) {
this.updated = updated;
}
@Override
public String toString() {
return "ResUserDto{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", created='" + created + '\'' +
", updated='" + updated + '\'' +
'}';
}
}
repository>BlogService
package com.example.blog.repository;
import com.example.blog.models.request.ReqJoin;
import com.example.blog.models.request.ReqLogin;
import com.example.blog.models.request.ReqPost;
import com.example.blog.models.response.Data;
import com.example.blog.models.response.ResDelete;
import com.example.blog.models.response.ResJoin;
import com.example.blog.models.response.ResLogin;
import com.example.blog.models.response.ResPost;
import com.example.blog.models.response.ResPostById;
import com.example.blog.models.response.ResUserDto;
import com.example.blog.utils.Define;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
public interface BlogService {
// @GET("/init/user")
// Call<ResUserDto> initUser();
//
// @GET("init/post")
// Call<ResPost> initPost();
@POST("join")
Call<ResJoin> join(@Body ReqJoin reqJoin);
@POST("login")
Call<ResLogin> login(@Body ReqLogin reqLogin);
@GET("post")
Call<ResPost> postList(@Header("Authorization") String token);
@GET("post/{postId}")
Call<ResPostById> postGetById(@Header("Authorization") String token, @Path("postId") int postId);
@PUT("post/{postId}")
Call<ResPostById> updatePost(@Header("Authorization") String token, @Path("postId") int postId, @Body ReqPost reqPost);
@DELETE("post/{postId}")
Call<ResDelete> deletePost(@Header("Authorization") String token, @Path("postId") int postId);
@POST("post")
Call<ResPostById> savePost(@Header("Authorization") String token, @Body ReqPost reqPost);
// 회원정보 한건 보기
@GET("user/{userId}")
Call<ResLogin> getUserInfo(@Header("Authorization") String token, @Path("userId") int userId);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Define.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
utils>Define
package com.example.blog.utils;
public class Define {
public static final String BASE_URL = "http://lalacoding.site/";
public static final String FILE_TOKEN = "token";
public static final String JWT = "jwt";
public static final String USER_ID = "userId";
}
utils>FragmentType
package com.example.blog.utils;
public enum FragmentType {
LIST, INFO, WEB_VIEW
}
utils>ToKenProvider
package com.example.blog.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class ToKenProvider {
// todo 기존 코드 수정하기
public static String getJWTToken(Context context, String fileType, String key) {
SharedPreferences preferences = context.getSharedPreferences(fileType, Context.MODE_PRIVATE);
return preferences.getString(key, "");
}
}
BlogDetailActivity
package com.example.blog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.example.blog.databinding.ActivityBlogDetailBinding;
import com.example.blog.models.request.ReqPost;
import com.example.blog.models.response.Data;
import com.example.blog.models.response.ResDelete;
import com.example.blog.models.response.ResPostById;
import com.example.blog.repository.BlogService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class BlogDetailActivity extends AppCompatActivity {
private static final String TAG = BlogDetailActivity.class.getName();
private ActivityBlogDetailBinding binding;
private BlogService service;
private String token;
private String userName;
private int postId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityBlogDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
addEventListener();
service = BlogService.retrofit.create(BlogService.class);
if (getIntent() != null) {
SharedPreferences preferences = getSharedPreferences("token", Context.MODE_PRIVATE);
token = preferences.getString("jwt", "");
userName = preferences.getString("username", "");
postId = getIntent().getIntExtra("postId", 0);
Log.d(TAG, "postId : " + postId);
service.postGetById(token, postId)
.enqueue(new Callback<ResPostById>() {
@Override
public void onResponse(Call<ResPostById> call, Response<ResPostById> response) {
if (response.isSuccessful()) {
ResPostById resPostById = response.body();
Log.d(TAG, resPostById.toString());
// 메서드
binding.titleTextView.setText(resPostById.data.title);
binding.detailContentEt.setText(resPostById.data.content);
// 버튼 보여주기 / 안보여주기
if (userName.equals(resPostById.data.user.getUsername())) {
binding.updateButton.setVisibility(View.VISIBLE);
binding.deleteButton.setVisibility(View.VISIBLE);
binding.detailContentEt.setEnabled(true);
} else {
binding.updateButton.setVisibility(View.GONE);
binding.deleteButton.setVisibility(View.GONE);
binding.detailContentEt.setEnabled(false);
}
}
}
@Override
public void onFailure(Call<ResPostById> call, Throwable t) {
}
});
}
}
private void addEventListener() {
binding.updateButton.setOnClickListener(v -> {
String title = binding.titleTextView.getText().toString();
String content = binding.detailContentEt.getText().toString();
service.updatePost(token, postId, new ReqPost(title, content))
.enqueue(new Callback<ResPostById>() {
@Override
public void onResponse(Call<ResPostById> call, Response<ResPostById> response) {
// 결과 처리
Log.d(TAG, response.code() + "< ---- code ");
}
@Override
public void onFailure(Call<ResPostById> call, Throwable t) {
}
});
});
binding.deleteButton.setOnClickListener(v -> {
service.deletePost(token, postId)
.enqueue(new Callback<ResDelete>() {
@Override
public void onResponse(Call<ResDelete> call, Response<ResDelete> response) {
Log.d(TAG, "status code :" + response.code() + "");
if (response.isSuccessful()) {
// 삭제 완료했으면
finish();
}
}
@Override
public void onFailure(Call<ResDelete> call, Throwable t) {
}
});
});
}
}
BlogListFragment
package com.example.blog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.blog.adapter.BlogListAdapter;
import com.example.blog.databinding.FragmentBlogListBinding;
import com.example.blog.interfaces.OnPageRequest;
import com.example.blog.interfaces.OnRefreshFragment;
import com.example.blog.models.response.ResPost;
import com.example.blog.repository.BlogService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class BlogListFragment extends Fragment implements OnPageRequest, OnRefreshFragment {
private FragmentBlogListBinding binding;
private BlogListAdapter adapter;
private BlogService service;
private String token;
public BlogListFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
service = BlogService.retrofit.create(BlogService.class);
SharedPreferences preferences = getActivity().getSharedPreferences("token", Context.MODE_PRIVATE);
token = preferences.getString("jwt", "");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentBlogListBinding.inflate(inflater, container, false);
initRecyclerView();
requestBlogList(false);
// 이벤트 처리 refreshLayout
binding.refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 1. 다시 화면을 그려 줘야 한다.
// 2. 하지만 서버에서 새로운 데이터를 가지고 와서 갱신해야 한다.
requestBlogList(true);
Log.d("TAG", "이벤트 확인 되었습니다.");
binding.refreshLayout.setRefreshing(false);
}
});
return binding.getRoot();
}
private void initRecyclerView() {
adapter = new BlogListAdapter(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
binding.blogListRecyclerView.setAdapter(adapter);
binding.blogListRecyclerView.setLayoutManager(linearLayoutManager);
binding.blogListRecyclerView.hasFixedSize();
binding.blogListRecyclerView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
}
});
}
public void requestBlogList(Boolean isRefresh) {
service.postList(token).enqueue(new Callback<ResPost>() {
@Override
public void onResponse(Call<ResPost> call, Response<ResPost> response) {
if (response.isSuccessful()) {
ResPost resPost = response.body();
if (isRefresh) {
// 추가적으로 받는 데이터와
adapter.refreshItems(resPost.data);
} else {
// 새롭게 갱신해야 되는 데이터 처리를 구분
adapter.addItems(resPost.data);
}
}
}
@Override
public void onFailure(Call<ResPost> call, Throwable t) {
}
});
}
/**
*
* @param postId : 해당 게시글 아이디 값
*/
@Override
public void onPageChange(int postId) {
Intent intent = new Intent(getContext(), BlogDetailActivity.class);
intent.putExtra("postId", postId);
startActivity(intent);
}
@Override
public void refresh() {
requestBlogList(true);
}
}
InfoFragment
package com.example.blog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.example.blog.models.response.ResLogin;
import com.example.blog.repository.BlogService;
import com.example.blog.utils.Define;
import com.example.blog.utils.ToKenProvider;
import com.example.blog.utils.ToKenProvider;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.example.blog.models.response.ResLogin;
import com.example.blog.repository.BlogService;
import com.example.blog.utils.Define;
import com.example.blog.utils.ToKenProvider;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class InfoFragment extends Fragment {
private BlogService service;
public InfoFragment() {
// Required empty public constructor
}
// DI -- 주니에 --> 반드시 알아야 되는 개념
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
service = BlogService.retrofit.create(BlogService.class);
// token
String token = ToKenProvider.getJWTToken(getContext(), Define.FILE_TOKEN, Define.JWT);
String userId = ToKenProvider.getJWTToken(getContext(), Define.FILE_TOKEN, Define.USER_ID);
service.getUserInfo(token, Integer.parseInt(userId))
.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
}
@Override
public void onFailure(Call<ResLogin> call, Throwable t) {
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View itemView = inflater.inflate(R.layout.fragment_info, container, false);
Button button = itemView.findViewById(R.id.logoutButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences preferences = getActivity().getSharedPreferences(Define.FILE_TOKEN, getActivity().MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString(Define.JWT, "");
editor.apply();
Intent intent = new Intent(getContext(), LoginActivity.class);
startActivity(intent);
getActivity().finish();
}
});
return itemView;
}
}
JoinActivity
package com.example.blog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.example.blog.databinding.ActivityJoinBinding;
import com.example.blog.models.request.ReqJoin;
import com.example.blog.models.response.ResJoin;
import com.example.blog.repository.BlogService;
import com.google.android.material.snackbar.Snackbar;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class JoinActivity extends AppCompatActivity {
private ActivityJoinBinding binding;
private BlogService service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityJoinBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
service = BlogService.retrofit.create(BlogService.class);
binding.joinButton.setOnClickListener(v -> {
String id = binding.joinEditText.getText().toString();
String pw = binding.joinPasswordEditText.getText().toString();
String email = binding.joinEmailEditText.getText().toString();
if (id.length() > 3 && pw.length() > 3 && email.length() > 3) {
service.join(new ReqJoin(id, pw, email))
.enqueue(new Callback<ResJoin>() {
@Override
public void onResponse(Call<ResJoin> call, Response<ResJoin> response) {
if (response.isSuccessful()) {
Snackbar.make(v, response.body().msg, Snackbar.LENGTH_SHORT).show();
binding.moveLoginTextView.callOnClick();
// 옵저버 팬턴 만들어서 (response.body().msg)
} else {
}
}
@Override
public void onFailure(Call<ResJoin> call, Throwable t) {
//
}
});
}
});
binding.moveLoginTextView.setOnClickListener(v -> {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
});
}
}
LoginActivity
package com.example.blog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.example.blog.databinding.ActivityLoginBinding;
import com.example.blog.models.request.ReqLogin;
import com.example.blog.models.response.ResLogin;
import com.example.blog.repository.BlogService;
import com.example.blog.utils.Define;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding binding;
BlogService service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 레트로핏 초기화
service = BlogService.retrofit.create(BlogService.class);
binding.loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String id = binding.loginEditText.getText().toString();
String pw = binding.loginPasswordEditText.getText().toString();
if(id.length() > 3 && pw.length() > 3) {
service.login(new ReqLogin(id, pw))
.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
if (response.isSuccessful()) {
ResLogin resLogin = response.body();
SharedPreferences preferences = getSharedPreferences(Define.FILE_TOKEN, MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString(Define.JWT, response.headers().get("Authorization"));
editor.putString("username", resLogin.data.username);
editor.putString("email", resLogin.data.email);
editor.putString(Define.USER_ID, String.valueOf(resLogin.data.id));
editor.apply();
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable t) {
Log.d("TAG", t.getMessage());
}
});
}
}
});
binding.moveSignUpTextView.setOnClickListener(v -> {
Intent intent = new Intent(this, JoinActivity.class);
startActivity(intent);
});
}
}
MainActivity
package com.example.blog;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import com.example.blog.databinding.ActivityMainBinding;
import com.example.blog.interfaces.OnRefreshFragment;
import com.example.blog.models.request.ReqJoin;
import com.example.blog.models.request.ReqLogin;
import com.example.blog.models.response.ResJoin;
import com.example.blog.models.response.ResLogin;
import com.example.blog.models.response.ResPost;
import com.example.blog.models.response.ResUserDto;
import com.example.blog.repository.BlogService;
import com.example.blog.utils.FragmentType;
import com.google.android.material.navigation.NavigationBarView;
import com.google.android.material.snackbar.Snackbar;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getName();
private ActivityMainBinding binding;
private BlogService service;
// 액티비티에서 다른 액티비티에 결과를 받기 위해 등록하는 녀석
private OnRefreshFragment onRefreshFragment;
private BlogListFragment blogListFragment;
private ActivityResultLauncher<Intent> startActivityResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent resultData = result.getData();
String msg = resultData.getStringExtra("msg");
Log.d(TAG, "결과값이 잘 돌아 왔습니다.");
Log.d(TAG, "msg :" + msg);
Snackbar.make(binding.getRoot(), msg, Snackbar.LENGTH_LONG).show();
if (blogListFragment != null) {
blogListFragment.requestBlogList(true);
}
//replaceFragment(FragmentType.LIST);
}
}
);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 통신 성공 여부 테스트
service = BlogService.retrofit.create(BlogService.class);
replaceFragment(FragmentType.LIST);
addBottomNaviListener();
addTopAppBarListener();
}
private void addTopAppBarListener() {
binding.topAppBar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
moveWriteActivity();
return true;
}
});
}
private void moveWriteActivity() {
Intent intent = new Intent(this, WriteActivity.class);
// startActivity(intent);
startActivityResult.launch(intent);
}
private void replaceFragment(FragmentType type) {
Fragment fragment = null;
switch (type) {
case LIST:
blogListFragment = new BlogListFragment();
fragment = blogListFragment;
break;
case INFO:
fragment = new InfoFragment();
break;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(binding.mainContainer.getId(), fragment);
transaction.commit();
}
private void addBottomNaviListener() {
binding.bottomNavigation.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {
@SuppressLint("NonConstantResourceId")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.page_1:
replaceFragment(FragmentType.LIST);
break;
case R.id.page_2:
replaceFragment(FragmentType.INFO);
break;
}
return true;
}
});
}
}
//4.
// ReqLogin loginObj = new ReqLogin("username321","4321");
// service.login(loginObj).enqueue(new Callback<ResLogin>() {
// @Override
// public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
// Log.d(TAG, "code : " +response.body());
// Log.d(TAG, "======================");
// Log.d(TAG, response.headers().get("Authorization"));
// Log.d(TAG, "======================");
// ResLogin resLogin = response.body();
// Log.d(TAG, "resLogin : " +resLogin);
// }
//
// @Override
// public void onFailure(Call<ResLogin> call, Throwable t) {
//
// }
// });
//3.
// service.join(joinObj).enqueue(new Callback<ResJoin>() {
// @Override
// public void onResponse(Call<ResJoin> call, Response<ResJoin> response) {
// Log.d(TAG, "code : " + response.code());
// Log.d(TAG, "body : " + response.body());
// ResJoin resJoin = response.body();
// Log.d(TAG, "resJoin : " + resJoin);
// }
//
// @Override
// public void onFailure(Call<ResJoin> call, Throwable t) {
//
// }
// });
//1.
// service.initUser().enqueue(new Callback<ResUserDto>() {
// @Override
// public void onResponse(Call<ResUserDto> call, Response<ResUserDto> response) {
// Log.d(TAG, "status code: " + response.code());
// }
//
// @Override
// public void onFailure(Call<ResUserDto> call, Throwable t) {
// Log.d(TAG, "onFailure : " + t.getMessage());
//
// }
// });
//2.
// service.initPost().enqueue(new Callback<ResPost>() {
// @Override
// public void onResponse(Call<ResPost> call, Response<ResPost> response) {
// Log.d(TAG, "status code: " + response.code());
// }
//
// @Override
// public void onFailure(Call<ResPost> call, Throwable t) {
// Log.d(TAG, "onFailure : " + t.getMessage());
// }
// });
//todo info 페이지는 사용자 정보 받기 처리하기
WebViewFragment
package com.example.blog;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
class WebViewFragment extends Fragment {
public WebViewFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_webview, container, false);
}
}
WriteActivity
package com.example.blog;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.example.blog.databinding.ActivityWriteBinding;
import com.example.blog.models.request.ReqPost;
import com.example.blog.models.response.ResPostById;
import com.example.blog.repository.BlogService;
import com.example.blog.utils.Define;
import com.example.blog.utils.ToKenProvider;
import com.google.android.material.snackbar.Snackbar;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class WriteActivity extends AppCompatActivity {
private ActivityWriteBinding binding;
private BlogService service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWriteBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
service = BlogService.retrofit.create(BlogService.class);
binding.saveButton.setOnClickListener(v -> {
String title = binding.titleEditText.getText().toString();
String content = binding.detailContentEt.getText().toString();
if(title.length() > 3 && content.length() > 3) {
requestPost(title, content);
}
});
}
// todo 파싱오류
private void requestPost(String title, String content) {
String token = ToKenProvider.getJWTToken(this, Define.FILE_TOKEN, Define.JWT);
service.savePost(token, new ReqPost(title, content))
.enqueue(new Callback<ResPostById>() {
@Override
public void onResponse(Call<ResPostById> call, Response<ResPostById> response) {
if(response.isSuccessful() && response.body() != null) {
String serverMsg = response.body().msg;
moveActivity(serverMsg);
} else {
Snackbar.make(binding.getRoot(),
"네트워크가 불완정합니다.",
Snackbar.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ResPostById> call, Throwable t) {
Log.d("TAG", t.getMessage());
}
});
}
private void moveActivity(String severMsg) {
Intent intent = new Intent();
intent.putExtra("msg", severMsg);
setResult(Activity.RESULT_OK, intent);
finish();
}
}
res>layout>activity_blog_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlogDetailActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="제목입니다."
android:textColor="@color/black"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/detailContentEt"
android:layout_width="match_parent"
android:layout_height="400dp"
android:gravity="start"
tools:text="content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView" />
<Button
android:id="@+id/updateButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:text="UPDATE"
app:layout_constraintEnd_toStartOf="@id/deleteButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/detailContentEt" />
<Button
android:id="@+id/deleteButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="DELETE"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/detailContentEt"
app:layout_constraintStart_toEndOf="@id/updateButton"
app:layout_constraintTop_toTopOf="@id/updateButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
res>layout>activity_join.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".JoinActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<TextView
android:id="@+id/joinPageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="회원가입 페이지"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/joinEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"
app:layout_constraintVertical_chainStyle="packed" />
<EditText
android:id="@+id/joinEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="an3232"
app:layout_constraintBottom_toTopOf="@id/joinPasswordEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/joinPageName" />
<EditText
android:id="@+id/joinPasswordEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:inputType="textPassword"
android:text="1234"
app:layout_constraintBottom_toTopOf="@id/joinEmailEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/joinEditText" />
<EditText
android:id="@+id/joinEmailEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:inputType="textEmailAddress"
android:text="user8_8@nate.com"
app:layout_constraintBottom_toTopOf="@id/joinButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/joinPasswordEditText" />
<Button
android:id="@+id/joinButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="회원가입하기"
app:layout_constraintBottom_toTopOf="@id/moveLoginTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/joinEmailEditText" />
<TextView
android:id="@+id/moveLoginTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="로그인 하기"
android:textColor="@color/info"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/joinButton" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="20dp"
tools:layout_editor_absoluteY="16dp">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
res>layout>activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<TextView
android:id="@+id/loginPageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="로그인 페이지"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/loginEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"
app:layout_constraintVertical_chainStyle="packed" />
<EditText
android:id="@+id/loginEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="an3232"
app:layout_constraintBottom_toTopOf="@id/loginPasswordEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginPageName" />
<EditText
android:id="@+id/loginPasswordEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:inputType="textPassword"
android:text="1234"
app:layout_constraintBottom_toTopOf="@id/loginButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginEditText" />
<Button
android:id="@+id/loginButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="로그인하기"
app:layout_constraintBottom_toTopOf="@id/moveSignUpTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginPasswordEditText" />
<TextView
android:id="@+id/moveSignUpTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="아직 회원가입이 안되어 있나요?"
android:textColor="@color/info"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
res>layout>activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/top_app_bar"
app:title="@string/app_name" />
<LinearLayout
android:id="@+id/mainContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/bottomNaviContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topAppBar" />
<LinearLayout
android:id="@+id/bottomNaviContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainContainer"
tools:layout_height="48dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_navigation_menu" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
res>layout>activity_write.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlogDetailActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<EditText
android:id="@+id/titleEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:hint="제목입니다." />
<EditText
android:id="@+id/detailContentEt"
android:layout_width="match_parent"
android:layout_height="400dp"
android:gravity="start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleEditText"
android:hint="내용을 입력해 주세요" />
<Button
android:id="@+id/saveButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="글 저장하기"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/detailContentEt" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
res>layout>fragment_blog_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlogListFragment">
<!--RecyclerView : adapter , -->
<androidx.recyclerview.widget.RecyclerView
tools:listitem="@layout/item_blog_card"
android:id="@+id/blogListRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
res>layout>fragment_Info.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".InfoFragment">
<Button
android:layout_gravity="center"
android:id="@+id/logoutButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="로그아웃"/>
</FrameLayout>
res>layout>fragment_webView.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WebViewFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
item_blog_card.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/blogImageView"
android:layout_width="match_parent"
android:layout_height="190dp"
android:layout_marginBottom="20dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/blogTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="12dp"
android:maxLines="2"
android:text="제목입니다."
android:textColor="@color/black"
android:textSize="20dp" />
<TextView
android:id="@+id/blogUserTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="12dp"
android:maxLines="2"
android:text="user name."
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/blogContentTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="12dp"
android:ellipsize="end"
android:maxLines="2"
android:text="contents"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
res>menu>bottom_navigation_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/page_1"
android:icon="@drawable/ic_baseline_movie_24"
android:title="@string/str_list" />
<item
android:id="@+id/page_2"
android:icon="@drawable/ic_baseline_info_24"
android:title="@string/str_info" />
</menu>
res>menu>top_app_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/favorite"
android:icon="@drawable/ic_baseline_favorite_24"
android:title="@string/str_favorite"
app:showAsAction="ifRoom" />
</menu>