- Original Nomadcoder site : https://nomadcoders.co/
- Web[ReactJS] Code : https://github.com/LEEJAECHEOL/nomad_clone_web
- BackEnd Code : https://github.com/LEEJAECHEOL/nomadCloneProject-bachend
- Android Studio 4.1.2
- Java JDK 1.8
- JWT Spring boot Server
- Mysql 8.0
- compileSdkVersion : 30
- minSdkVersion : 21
- targetSdkVersion : 30
- Google OAuth
- Vimeo
- Summernote
- Firebase
- Retrofit
- Glide
- 채널톡 Channel.io
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//roundedimageview
implementation 'com.makeramen:roundedimageview:2.3.0'
//lombok
compileOnly 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
//fontawesome
implementation 'com.github.gmazzo:fontawesome:0.4'
implementation 'info.androidhive:fontawesome:0.0.5'
//recyclerview
implementation 'com.thoughtbot:expandablerecyclerview:1.4'
//summernote
implementation 'in.nashapp.androidsummernote:androidsummernote:1.0.5'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
//Firebase
implementation platform('com.google.firebase:firebase-bom:26.7.0')
implementation 'com.google.firebase:firebase-auth'
implementation 'com.firebaseui:firebase-ui-auth:6.4.0'
// gson library
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
//glide
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.github.chinalwb:are:0.1.7'
//iamport
implementation 'com.github.iamport:iamport-android:v0.0.6-dev21'
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.30"
implementation 'commons-io:commons-io:2.4'
//channel.io
implementation 'com.zoyi.channel:plugin-android:8.3.0'
}
/* root */
create user 'nomad'@'%' identified by '1234';
GRANT ALL PRIVILEGES ON *.* TO 'nomad'@'%';
create database nomad;
use nomad;
Replace with your IP address in the address place of the code before start!
public static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://${Add your IP address!!}:8080/")
.addConverterFactory(GsonConverterFactory.create())
.build();
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/nomad?serverTimezone=Asia/Seoul
username: nomad
password: 1234
jpa:
hibernate:
ddl-auto: update #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
properties:
hibernate.format_sql: true
jackson:
serialization:
fail-on-empty-beans: false
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
enabled: true
file:
path: C:/Users/Dong/Desktop/NomadWork/nomadCloneProject-bachend/src/main/resources/uploads/
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
OAuthApi oauthApi = OAuthApi.retrofit.create(OAuthApi.class);
Call<CMRespDto<LoginDto>> call = oauthApi.postOauth(account.getIdToken());
call.enqueue(new Callback<CMRespDto<LoginDto>>() {
@Override
public void onResponse(Call<CMRespDto<LoginDto>> call, Response<CMRespDto<LoginDto>> response) {
Log.d(TAG, "onResponse: 응답 : "+response.body());
if (response.body().getStatusCode()==200){
Log.d(TAG, "onResponse: 200 성공");
LoginDto loginDto = response.body().getData();
Gson gson = new Gson();
String basicUserInfo = gson.toJson(loginDto);
Log.d(TAG, "onResponse: data : 구글로그인 " + loginDto);
pref = getSharedPreferences("pref", MODE_PRIVATE);
editor = pref.edit();
editor.putString("token",loginDto.getToken());
editor.putString("user",basicUserInfo);
editor.commit();
finish();
} else {
Log.d(TAG, "onResponse: 로그인 실패");
}
}
@Override
public void onFailure(Call<CMRespDto<LoginDto>> call, Throwable t) {
Log.d(TAG, "onFailure: onFailure");
}
});
} catch (ApiException e) {
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
}
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.sign_in_button:
signIn();
break;
// ...
}
}
private void signIn() {
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
//init settings
private void init(){
//get token
pref = getSharedPreferences("pref",MODE_PRIVATE);
token = pref.getString("token","");
// appbar
ivBack = findViewById(R.id.iv_back);
tvToolbarTitle = findViewById(R.id.tv_toolbar_title);
// level chips
chipBeginner = findViewById(R.id.chip_beginner);
chipIntermediate = findViewById(R.id.chip_intermediate);
chipAdvanced = findViewById(R.id.chip_advanced);
// pay chips
chipFree = findViewById(R.id.chip_free);
chipPaid = findViewById(R.id.chip_paid);
// chipGroup
chipGroup1 = findViewById(R.id.chipGroup);
chipGroup2 = findViewById(R.id.chipGroup2);
// select cancel buttons
btnTechCancel = findViewById(R.id.btn_tech_cancel);
btnLevelCancel = findViewById(R.id.btn_level_cancel);
btnPriceCancel = findViewById(R.id.btn_price_cancel);
// subtitle, title
tvCoursesTitle = findViewById(R.id.tv_title);
tvCoursesSubTitle = findViewById(R.id.tv_subtitle);
// title, subtitle set text
tvCoursesTitle.setText("All Courses");
tvCoursesSubTitle.setText("초급부터 고급까지! 니꼬쌤과 함께 풀스택으로 성장하세요!");
// appbar's title set text
tvToolbarTitle.setText("Courses");
// recyclerview
rvCoursesList = findViewById(R.id.rv_courses_list);
rvTech = findViewById(R.id.rv_tech);
// back
ivBack.setOnClickListener(v -> {
finish();
});
rivUser = findViewById(R.id.riv_user);
//초기세팅
coursesSort.setIsFree("");
coursesSort.setLevel("");
coursesSort.setTechId(0);
}
// download courses list
public void downloadCourses(){
LinearLayoutManager manager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
rvCoursesList.setLayoutManager(manager);
Call<CMRespDto<List<CoursesPreview>>> call = nomadApi.getAllCourses(coursesSort.getLevel(),coursesSort.getIsFree(),coursesSort.getTechId());
call.enqueue(new Callback<CMRespDto<List<CoursesPreview>>>() {
@Override
public void onResponse(Call<CMRespDto<List<CoursesPreview>>> call, Response<CMRespDto<List<CoursesPreview>>> response) {
Log.d(TAG, "onResponse: "+response.body());
List<CoursesPreview> coursesPreviews = response.body().getData();
rvCoursesList.setAdapter(new CoursesAdapter(coursesPreviews,mContext, token));
}
@Override
public void onFailure(Call<CMRespDto<List<CoursesPreview>>> call, Throwable t) {
Log.d(TAG, "onFailure: ");
}
});
}
// download tech list
private void downloadTech(){
GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext,4);
rvTech.setLayoutManager(gridLayoutManager);
NomadApi nomadApi = NomadApi.retrofit.create(NomadApi.class);
Call<CMRespDto<List<Tech>>> call = nomadApi.getTechList();
call.enqueue(new Callback<CMRespDto<List<Tech>>>() {
@Override
public void onResponse(Call<CMRespDto<List<Tech>>> call, Response<CMRespDto<List<Tech>>> response) {
Log.d(TAG, "onResponse: 성공 " + response.body());
List<Tech> teches = response.body().getData();
Log.d(TAG, "onResponse: techs : "+teches);
rvTech.setAdapter(new TechAdapter(teches,mContext,btnTechCancel));
}
@Override
public void onFailure(Call<CMRespDto<List<Tech>>> call, Throwable t) {
Log.d(TAG, "onFailure: 실패");
}
});
}
private void buttonListener(){
materialButtonToggleGroup.addOnButtonCheckedListener(new MaterialButtonToggleGroup.OnButtonCheckedListener() {
@Override
public void onButtonChecked(MaterialButtonToggleGroup group, int checkedId, boolean isChecked) {
if(isChecked){
if(checkedId == R.id.btn_sort_popular){
sort="popular";
addFrag();
initScrollListener();
}else if(checkedId == R.id.btn_sort_new){
sort="new";
addFrag();
initScrollListener();
}
}
}
});
}
private void addFrag(){
page=0;
nomadApi = NomadApi.retrofit.create(NomadApi.class);
Call<CMRespDto<List<CommunityListRespDto>>> call2= nomadApi.comFindAll("Bearer "+token,sort,categoryId,page);
call2.enqueue(new Callback<CMRespDto<List<CommunityListRespDto>>>() {
@Override
public void onResponse(Call<CMRespDto<List<CommunityListRespDto>>> call, Response<CMRespDto<List<CommunityListRespDto>>> response) {
communities= response.body().getData();
communityAdapter = new CommunityAdapter(communities, mContext,token);
rvCommunityNew.setAdapter(communityAdapter);
rvCommunityNew.setLayoutManager(manager);
}
@Override
public void onFailure(Call<CMRespDto<List<CommunityListRespDto>>> call, Throwable t) {
Log.d(TAG, "onFailure: 실패");
}
});
}
private void initScrollListener(){
rvCommunityNew.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(!rvCommunityNew.canScrollVertically(1)){
if(communities.size()%10>0){
progressBar.setVisibility(View.INVISIBLE);
}else{
progressBar.setVisibility(View.VISIBLE);
}
if(page<(communities.size()/10)){ //page무한 증가 방지
page++;
Call<CMRespDto<List<CommunityListRespDto>>> call2= nomadApi.comFindAll("Bearer "+token,sort,categoryId,page);
call2.enqueue(new Callback<CMRespDto<List<CommunityListRespDto>>>() {
@Override
public void onResponse(Call<CMRespDto<List<CommunityListRespDto>>> call, Response<CMRespDto<List<CommunityListRespDto>>> response) {
for(int i=0;i<response.body().getData().size();i++){
communities.add(response.body().getData().get(i));
communityAdapter.notifyDataSetChanged();
}
}
@Override
public void onFailure(Call<CMRespDto<List<CommunityListRespDto>>> call, Throwable t) {
Log.d(TAG, "onFailure: 실패");
}
});
}
Log.d(TAG, "onScrollStateChanged: Page: "+page+" communities.size()"+communities.size());
}else{
progressBar.setVisibility(View.INVISIBLE);
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate( R.layout.video_frag_detail, container, false );
context = container.getContext();
pref = context.getSharedPreferences("pref",Context.MODE_PRIVATE);
String token = pref.getString("token","");
tvVideoTitle = view.findViewById(R.id.tv_video_title);
tvVideoTitle.setText(videoContent.getTitle());
videoView = view.findViewById(R.id.video_view);
videoView.getSettings().setJavaScriptEnabled(true);
videoView.getSettings().setDomStorageEnabled(true);
Log.d(TAG, "onCreateView: 비메오아이디"+videoContent.getVimeoId());
String url = "http://192.168.43.74:3100/android/video/"+videoContent.getVimeoId();
Map<String, String> additionalHttpHeader = new HashMap<>();
additionalHttpHeader.put("Authorization","Bearer "+token);
videoView.loadUrl(url, additionalHttpHeader);
videoView.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
System.out.println("is run?");
}
});
private void init(){
Intent intent = getIntent();
course = (Course) intent.getSerializableExtra("course");
// 토큰 가져오기
pref=getSharedPreferences("pref", MODE_PRIVATE);
token = pref.getString("token","");
// 뒤로가기 버튼
ivBack = findViewById(R.id.iv_back);
ivBack.setOnClickListener(v -> {
finish();
});
// 앱바 디자인
tvToolbarTitle = findViewById(R.id.tv_toolbar_title);
tvToolbarTitle.setText("Payment");
ivCourse = findViewById(R.id.iv_course);
tvCourseTitle = findViewById(R.id.tv_course_title);
tvCourseSubTitle = findViewById(R.id.tv_course_subtitle);
tvCourseLevel = findViewById(R.id.tv_courses_level);
tvAmountPayment = findViewById(R.id.tv_amount_payment);
rgPaymentMethod = findViewById(R.id.rg_payment_method);
btnPayNow = findViewById(R.id.btn_pay_now);
}
private void setItem(){
tvCourseLevel.setVisibility(View.INVISIBLE);
tvCourseTitle.setText(course.getTitle());
tvCourseSubTitle.setText(course.getSubTitle());
Map<String,Object> previewImage = course.getPreviewImage();
Glide
.with(this)
.load(previewImage.get("url")) // 임시 테스트로 넘어오는 이미지는 localhost라서 적용이 안됨
.centerCrop()
.placeholder(R.drawable.course_youtube)
.into(ivCourse);
tvAmountPayment.setText("₩ "+course.getPrice());
rgPaymentMethod.setOnCheckedChangeListener((group, checkedId) -> {
if (group==rgPaymentMethod){
if (checkedId == R.id.rb_domestic_card){
Log.d(TAG, "setItem: 국내카드");
btnPayNow.setOnClickListener(v -> {
payMethod = "domestic_card";
Intent intent = new Intent(this, IamportActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("course",course);
intent.putExtra("payMethod",payMethod);
startActivity(intent);
});
} else if(checkedId == R.id.rb_kakao_pay){
Log.d(TAG, "setItem: 카카오페이");
btnPayNow.setOnClickListener(v -> {
payMethod = "kakao_pay";
Intent intent = new Intent(this, IamportActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("course",course);
intent.putExtra("payMethod",payMethod);
startActivity(intent);
});
} else if(checkedId == R.id.rb_overseas_card){
Log.d(TAG, "setItem: 해외카드");
btnPayNow.setOnClickListener(v -> {
payMethod = "overseas_card";
Intent intent = new Intent(this, IamportActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("course",course);
intent.putExtra("payMethod",payMethod);
startActivity(intent);
});
} else{
Log.d(TAG, "setItem: 선택안함");
Toast.makeText(this.getApplicationContext(),"결제방법을 체크해주세요",Toast.LENGTH_SHORT).show();
}
}
});
}
위와같은 error가 발생한다면
nomad-clone-app/gradle/wrapper/gradle-wrapper.properties에서
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
의 버전을
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
로 낮추고
dependencies {
classpath "com.android.tools.build:gradle:4.0.2"
classpath 'com.google.gms:google-services:4.3.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
gradle버전을 4.1.2에서 4.0.2로 낮추고 Sync Now를 눌러 적용해 준다