ログイン仮実装 #79

Merged
Fujimatsu merged 25 commits from feature/add_login into main 2024-06-22 12:13:19 +00:00
13 changed files with 334 additions and 1 deletions

View File

@ -45,6 +45,8 @@ dependencies {
implementation project(':feature:child') implementation project(':feature:child')
implementation project(':feature:setting') implementation project(':feature:setting')
implementation project(':data')
implementation project(':shared') implementation project(':shared')
implementation project(':utils') implementation project(':utils')
@ -57,4 +59,8 @@ dependencies {
implementation libs.navigation.fragment implementation libs.navigation.fragment
implementation libs.navigation.ui implementation libs.navigation.ui
implementation libs.navigation.dynamic.features.fragment implementation libs.navigation.dynamic.features.fragment
// Retrofit
implementation libs.retrofit
implementation libs.converter.gson
} }

View File

@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".KidShiftApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@ -10,9 +13,11 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:name=".KidShiftApplication"
android:theme="@style/Theme.KidShift" android:theme="@style/Theme.KidShift"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".LoginActivity"
android:exported="false" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"> android:exported="true">

View File

@ -0,0 +1,104 @@
package one.nem.kidshift;
import android.os.Bundle;
import android.widget.EditText;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginResponse;
import one.nem.kidshift.utils.KSLogger;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@AndroidEntryPoint
public class LoginActivity extends AppCompatActivity {
@Inject
KSLogger logger;
@Inject
UserSettings userSettings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_login);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// Retrofit init
KidShiftApiService apiService = new Retrofit.Builder()
.baseUrl("https://kidshift-beta.nem.one/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(KidShiftApiService.class);
EditText emailEditText = findViewById(R.id.emailEditText);
EditText passwordEditText = findViewById(R.id.passwordEditText);
findViewById(R.id.loginButton).setOnClickListener(v -> {
CompletableFuture.supplyAsync(() -> {
try {
Response<ParentLoginResponse> response = apiService.parentLogin(
new ParentLoginRequest(
emailEditText.getText().toString(),
passwordEditText.getText().toString()
)).execute();
return response;
} catch (IOException e) {
logger.error("IOException");
throw new RuntimeException(e);
}
}).thenAccept(response -> {
if (response.isSuccessful()) {
logger.info("Login Success");
logger.debug("AccessToken: " + response.body().getAccessToken());
UserSettings.AppCommonSetting appCommonSetting = userSettings.getAppCommonSetting();
appCommonSetting.setLoggedIn(true);
appCommonSetting.setAccessToken(response.body().getAccessToken());
appCommonSetting.setChildMode(false);
finish();
} else {
logger.error("Login Failed");
try {
logger.debug("Response: " + response.errorBody().string());
} catch (IOException e) {
logger.error("IOException while reading error body");
}
// ログイン失敗時の処理
}
}).exceptionally(e -> {
logger.error("Exception occurred: " + e.getMessage());
return null;
});
});
// for Debug
findViewById(R.id.loginButton).setOnLongClickListener(v -> {
// ログイン画面をバイパスしてメイン画面に遷移
finish();
return true;
});
}
}

View File

@ -1,5 +1,6 @@
package one.nem.kidshift; package one.nem.kidshift;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
@ -17,6 +18,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
import javax.inject.Inject; import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint; import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
@AndroidEntryPoint @AndroidEntryPoint
@ -25,6 +27,9 @@ public class MainActivity extends AppCompatActivity {
@Inject @Inject
KSLogger ksLogger; KSLogger ksLogger;
@Inject
UserSettings userSettings;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -51,5 +56,15 @@ public class MainActivity extends AppCompatActivity {
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
// Check logged in
if (userSettings.getAppCommonSetting().isLoggedIn()) {
ksLogger.info("User is logged in!");
} else {
ksLogger.info("User is not logged in!");
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
} }
} }

View File

@ -0,0 +1,53 @@
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<LinearLayout
android:id="@+id/inputContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/emailEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword" />
</LinearLayout>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="※Loginボタン長押しでBypass"
app:layout_constraintBottom_toTopOf="@+id/loginButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/loginButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="LOGIN"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -43,4 +43,8 @@ dependencies {
implementation project(':model') implementation project(':model')
implementation project(':utils') implementation project(':utils')
// Retrofit
implementation libs.retrofit
implementation libs.converter.gson
} }

View File

@ -1,9 +1,23 @@
package one.nem.kidshift.data; package one.nem.kidshift.data;
import one.nem.kidshift.model.ParentModel;
public interface UserSettings { public interface UserSettings {
ApiSetting getApiSetting(); ApiSetting getApiSetting();
TaskSetting getTaskSetting(); TaskSetting getTaskSetting();
AppCommonSetting getAppCommonSetting();
interface AppCommonSetting {
boolean isLoggedIn();
void setLoggedIn(boolean loggedIn);
String getAccessToken();
void setAccessToken(String token);
boolean isChildMode();
void setChildMode(boolean childMode);
}
interface ApiSetting { interface ApiSetting {
String getApiBaseUrl(); String getApiBaseUrl();

View File

@ -18,6 +18,11 @@ public class UserSettingsDummyImpl implements UserSettings {
return new TaskSettingImpl(); return new TaskSettingImpl();
} }
@Override
public AppCommonSetting getAppCommonSetting() {
return null;
}
@Override @Override
public UserSettings.ApiSetting getApiSetting() { public UserSettings.ApiSetting getApiSetting() {
return new ApiSettingImpl(); return new ApiSettingImpl();

View File

@ -30,6 +30,72 @@ public class UserSettingsImpl implements UserSettings {
return new TaskSettingImpl(); return new TaskSettingImpl();
} }
@Override
public AppCommonSetting getAppCommonSetting() {
return new AppCommonSettingImpl();
}
public class AppCommonSettingImpl implements UserSettings.AppCommonSetting {
transient
SharedPrefUtils sharedPrefUtils;
boolean loggedIn;
String accessToken;
boolean childMode;
AppCommonSettingImpl() {
sharedPrefUtils = sharedPrefUtilsFactory.create("user_settings");
AppCommonSettingImpl appCommonSetting = sharedPrefUtils.getObject("app_common_setting", AppCommonSettingImpl.class);
if (appCommonSetting != null) {
loggedIn = appCommonSetting.isLoggedIn();
accessToken = appCommonSetting.getAccessToken().isEmpty() ? "" : appCommonSetting.getAccessToken();
childMode = appCommonSetting.isChildMode();
} else {
loggedIn = false;
accessToken = "";
childMode = false;
}
}
private void save() {
sharedPrefUtils.saveObject("app_common_setting", this);
}
@Override
public boolean isLoggedIn() {
return loggedIn;
}
@Override
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
save();
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public void setAccessToken(String token) {
accessToken = token;
save();
}
@Override
public boolean isChildMode() {
return childMode;
}
@Override
public void setChildMode(boolean childMode) {
this.childMode = childMode;
save();
}
}
public class ApiSettingImpl implements UserSettings.ApiSetting { public class ApiSettingImpl implements UserSettings.ApiSetting {
transient transient

View File

@ -0,0 +1,14 @@
package one.nem.kidshift.data.retrofit;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface KidShiftApiService {
@POST("/parent/auth/login")
Call<ParentLoginResponse> parentLogin(@Body ParentLoginRequest request);
}

View File

@ -0,0 +1,27 @@
package one.nem.kidshift.data.retrofit.model.parent.auth;
public class ParentLoginRequest {
private String email;
private String password;
public ParentLoginRequest(String email, String password) {
this.email = email;
this.password = password;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,17 @@
package one.nem.kidshift.data.retrofit.model.parent.auth;
public class ParentLoginResponse {
private String accessToken;
public ParentLoginResponse(String accessToken) {
this.accessToken = accessToken;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}

View File

@ -9,8 +9,10 @@ material = "1.12.0"
activity = "1.9.0" activity = "1.9.0"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
nav = "2.7.7" nav = "2.7.7"
retrofit = "2.11.0"
[libraries] [libraries]
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@ -26,6 +28,7 @@ com-google-dagger-hilt-compiler = { group="com.google.dagger", name="hilt-compil
navigation-fragment = { group="androidx.navigation", name="navigation-fragment", version.ref="nav"} navigation-fragment = { group="androidx.navigation", name="navigation-fragment", version.ref="nav"}
navigation-ui = { group="androidx.navigation", name="navigation-ui", version.ref="nav"} navigation-ui = { group="androidx.navigation", name="navigation-ui", version.ref="nav"}
navigation-dynamic-features-fragment = { group="androidx.navigation", name="navigation-dynamic-features-fragment", version.ref="nav"} navigation-dynamic-features-fragment = { group="androidx.navigation", name="navigation-dynamic-features-fragment", version.ref="nav"}
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
[plugins] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }