Merge branch 'main' into feature/add_task_menu

# Conflicts:
#	gradle/libs.versions.toml
This commit is contained in:
ろむねこ 2024-06-28 12:46:03 +09:00
commit cfbaedd740
Signed by: Fujimatsu
GPG Key ID: FA1F39A1BA37D168
68 changed files with 1747 additions and 437 deletions

View File

@ -1 +1,8 @@
# WIP
# WIP
## メモ
- リリース前(=提出前)には`DEBUG_ONLY`で検索してチェック(念のため)
## リリース前チェック
- DBの破壊的マイグレーションを許可するオプションを無効に
-

View File

@ -45,6 +45,8 @@ dependencies {
implementation project(':feature:child')
implementation project(':feature:setting')
implementation project(':data')
implementation project(':shared')
implementation project(':utils')
@ -57,4 +59,8 @@ dependencies {
implementation libs.navigation.fragment
implementation libs.navigation.ui
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"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".KidShiftApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -10,9 +13,11 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".KidShiftApplication"
android:theme="@style/Theme.KidShift"
tools:targetApi="31">
<activity
android:name=".LoginActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">

View File

@ -1,7 +1,6 @@
package one.nem.kidshift;
import android.app.Application;
import android.util.Log;
import com.google.android.material.color.DynamicColors;
@ -24,13 +23,6 @@ public class KidShiftApplication extends Application {
public void onCreate() {
super.onCreate();
// if(DynamicColors.isDynamicColorAvailable()) {
// Log.d("StartUp/DynamicColors", "DynamicColors is available!");
// DynamicColors.applyToActivitiesIfAvailable(this);
// } else {
// Log.d("StartUp/DynamicColors", "DynamicColors is not available.");
// }
logger.setTag("KidShiftApplication");
logger.info("super.onCreate() completed");

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;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
@ -17,6 +18,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.utils.KSLogger;
@AndroidEntryPoint
@ -25,6 +27,9 @@ public class MainActivity extends AppCompatActivity {
@Inject
KSLogger ksLogger;
@Inject
UserSettings userSettings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -51,5 +56,22 @@ public class MainActivity extends AppCompatActivity {
} catch (Exception e) {
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);
}
}
/**
* 起動時にバックグラウンドで行う各種更新処理とか
*/
private void startup() {
}
}

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

@ -39,8 +39,16 @@ dependencies {
annotationProcessor libs.com.google.dagger.hilt.compiler
// Java Faker
implementation 'com.github.javafaker:javafaker:1.0.2'
implementation libs.javafaker
implementation project(':model')
implementation project(':utils')
// Retrofit
implementation libs.retrofit
implementation libs.converter.gson
// Room
implementation libs.androidx.room.runtime
annotationProcessor libs.androidx.room.compiler
}

View File

@ -1,6 +1,7 @@
package one.nem.kidshift.data;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.model.ChildModel;
@ -11,13 +12,13 @@ public interface ChildData {
* @param childId 子ID
* @return ChildModel 子ユーザー情報
*/
ChildModel getChild(String childId);
CompletableFuture<ChildModel> getChild(String childId);
/**
* 子ユーザー一覧取得
* @return List<ChildModel> 子ユーザー一覧
*/
List<ChildModel> getChildList();
CompletableFuture<List<ChildModel>> getChildList();
/**
* 子ユーザー情報更新
@ -42,5 +43,5 @@ public interface ChildData {
* @param childId 子ID
* @return int ログインコード
*/
int issueLoginCode(String childId);
CompletableFuture<Integer> issueLoginCode(String childId);
}

View File

@ -0,0 +1,22 @@
package one.nem.kidshift.data;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.model.ParentModel;
/**
* データの同期など, ユーザーからの操作に基づかない処理を行う
*/
public interface KSActions {
CompletableFuture<TaskListResponse> syncTasks();
void syncChildList();
/**
* 親ユーザー情報同期
*/
CompletableFuture<ParentModel> syncParent();
}

View File

@ -1,15 +1,18 @@
package one.nem.kidshift.data;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback;
public interface ParentData {
/**
* 親ユーザー情報取得
* @param parentId 親ID
* @return ParentModel 親ユーザー情報
* @return 親ユーザー情報
*/
ParentModel getParent(String parentId);
CompletableFuture<ParentModel> getParent(ParentModelCallback callback);
/**
* 親ユーザー情報更新

View File

@ -1,9 +1,11 @@
package one.nem.kidshift.data;
import java.util.concurrent.CompletableFuture;
public interface RewardData {
/**
* 現時点の合計報酬額を取得する
* @return Integer 合計報酬額
*/
Integer getTotalReward();
CompletableFuture<Integer> getTotalReward();
}

View File

@ -1,6 +1,7 @@
package one.nem.kidshift.data;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.model.tasks.TaskItemModel;
@ -10,9 +11,16 @@ public interface TaskData {
/**
* 存在する全てのタスクを取得する
* @return List<TaskItemModel> タスクリスト
* @return CompletableFuture<List<TaskItemModel>> タスクリスト
*/
List<TaskItemModel> getTasks();
CompletableFuture<List<TaskItemModel>> getTasks();
/**
* アタッチされている全てのタスクを取得する
* @param childId 子ID
* @return CompletableFuture<List<TaskItemModel>> タスクリスト
*/
CompletableFuture<List<TaskItemModel>> getTasks(String childId);
/**
* タスクを追加する
@ -39,7 +47,7 @@ public interface TaskData {
* @param taskId タスクID
* @return TaskItemModel タスク
*/
TaskItemModel getTask(String taskId);
CompletableFuture<TaskItemModel> getTask(String taskId);
/**
* タスクの完了を記録する

View File

@ -1,9 +1,40 @@
package one.nem.kidshift.data;
import one.nem.kidshift.model.ParentModel;
public interface UserSettings {
interface Task {
ApiSetting getApiSetting();
TaskSetting getTaskSetting();
AppCommonSetting getAppCommonSetting();
SharedPrefCache getCache();
interface AppCommonSetting {
boolean isLoggedIn();
void setLoggedIn(boolean loggedIn);
String getAccessToken();
void setAccessToken(String token);
boolean isChildMode();
void setChildMode(boolean childMode);
}
interface SharedPrefCache {
ParentModel getParent();
void setParent(ParentModel parent);
}
interface ApiSetting {
String getApiBaseUrl();
void setApiBaseUrl(String url);
}
interface TaskSetting {
int getDefaultIconColor();
void setDefaultIconColor(int color);
String getDefaultIconEmoji();
void setDefaultIconEmoji(String emoji);
}
}

View File

@ -1,53 +0,0 @@
package one.nem.kidshift.data.impl;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.model.ChildModel;
public class ChildDataDummyImpl implements ChildData {
@Inject
public ChildDataDummyImpl() {
}
@Override
public ChildModel getChild(String childId) {
return null;
}
@Override
public List<ChildModel> getChildList() {
// 仮置きデータを生成する
List<ChildModel> childList = new ArrayList<>();
childList.add(new ChildModel("1", "子供1", "idididididid"));
childList.add(new ChildModel("2", "子供2", "idididididid"));
childList.add(new ChildModel("3", "子供3", "idididididid"));
return childList;
}
@Override
public void updateChild(ChildModel child) {
}
@Override
public void addChild(ChildModel child) {
}
@Override
public void removeChild(String childId) {
}
@Override
public int issueLoginCode(String childId) {
return 123456;
}
}

View File

@ -0,0 +1,77 @@
package one.nem.kidshift.data.impl;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.inject.Inject;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.child.ChildListResponse;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.utils.KSLogger;
import retrofit2.Call;
import retrofit2.Response;
public class ChildDataImpl implements ChildData {
private KidShiftApiService kidShiftApiService;
private KSLogger logger;
@Inject
public ChildDataImpl(KidShiftApiService kidShiftApiService, KSLogger logger) {
this.kidShiftApiService = kidShiftApiService;
this.logger = logger;
}
@Override
public CompletableFuture<ChildModel> getChild(String childId) {
return null;
}
@Override
public CompletableFuture<List<ChildModel>> getChildList() { // TODO-rca: DBにキャッシュするように修正する
return CompletableFuture.supplyAsync(() -> {
Call<ChildListResponse> call = kidShiftApiService.getChildList();
try {
Response<ChildListResponse> response = call.execute();
if (!response.isSuccessful()) return null; // TODO-rca: nullとするかは検討
ChildListResponse body = response.body();
if (body == null) return null;
return body.getList().stream().map(child -> {
ChildModel model = new ChildModel();
model.setDisplayName(child.getName().isEmpty() ? child.getId() : child.getName());
model.setInternalId(child.getId());
// 他のプロパティも処理する
return model;
}).collect(Collectors.toList());
} catch (Exception e) {
logger.error(e.getMessage());
return null;
}
});
}
@Override
public void updateChild(ChildModel child) {
}
@Override
public void addChild(ChildModel child) {
}
@Override
public void removeChild(String childId) {
}
@Override
public CompletableFuture<Integer> issueLoginCode(String childId) {
return null;
}
}

View File

@ -0,0 +1,90 @@
package one.nem.kidshift.data.impl;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.utils.KSLogger;
import retrofit2.Call;
import retrofit2.Response;
public class KSActionsImpl implements KSActions {
private UserSettings userSettings;
private KidShiftApiService kidShiftApiService;
private KSLogger logger;
@Inject
public KSActionsImpl(UserSettings userSettings, KidShiftApiService kidShiftApiService, KSLogger logger) {
this.userSettings = userSettings;
this.kidShiftApiService = kidShiftApiService;
this.logger = logger;
logger.setTag("KSActions");
}
@Override
public CompletableFuture<TaskListResponse> syncTasks() {
return CompletableFuture.supplyAsync(() -> {
Call<TaskListResponse> call = kidShiftApiService.getTasks();
try {
Response<TaskListResponse> response = call.execute();
if (!response.isSuccessful()) {
logger.error("Error fetching tasks: " + response.errorBody().string());
throw new RuntimeException("Error fetching tasks: " + response.errorBody().string());
}
TaskListResponse responseBody = response.body();
logger.info("Tasks fetched with status: " + response.code());
logger.debug("Tasks: " + responseBody.getList());
// // Save to cache
// userSettings.getCache().setTasks(responseBody.getList());
// logger.info("Tasks saved to cache");
return responseBody;
} catch (Exception e) {
logger.error("Error fetching tasks");
throw new RuntimeException(e);
}
});
}
@Override
public void syncChildList() {
}
@Override
public CompletableFuture<ParentModel> syncParent() {
logger.info("syncParent called and started");
return CompletableFuture.supplyAsync(() -> {
logger.info("fetching...");
Call<ParentInfoResponse> call = kidShiftApiService.getParentInfo();
try {
Response<ParentInfoResponse> response = call.execute();
if (!response.isSuccessful()) {
logger.error("Error fetching parent info: " + response.errorBody().string());
throw new RuntimeException("Error fetching parent info: " + response.errorBody().string());
}
ParentInfoResponse responseBody = response.body();
ParentModel parent = new ParentModel();
// TODO: 詰め替えをどこかにまとめる, 他のプロパティも処理する
parent.setInternalId(responseBody.getId());
parent.setEmail(responseBody.getEmail());
parent.setDisplayName(responseBody.getEmail()); // Workaround
logger.info("Parent fetched with status: " + response.code());
logger.debug("Parent: " + parent);
// Save to cache
userSettings.getCache().setParent(parent);
logger.info("Parent saved to cache");
return parent;
} catch (Exception e) {
logger.error("Error fetching parent info");
throw new RuntimeException(e);
}
});
}
}

View File

@ -1,23 +0,0 @@
package one.nem.kidshift.data.impl;
import javax.inject.Inject;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.model.ParentModel;
public class ParentDataDummyImpl implements ParentData {
@Inject
public ParentDataDummyImpl() {
}
@Override
public ParentModel getParent(String parentId) {
return new ParentModel("ID", "Parent Name", "homeGroupId", "hoge@example.com");
}
@Override
public void updateParent(ParentModel parent) {
}
}

View File

@ -0,0 +1,63 @@
package one.nem.kidshift.data.impl;
import android.util.Log;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback;
import one.nem.kidshift.utils.KSLogger;
import retrofit2.Call;
import retrofit2.Response;
public class ParentDataImpl implements ParentData {
private KidShiftApiService kidshiftApiService;
private UserSettings userSettings;
private KSLogger logger;
private KSActions ksActions;
@Inject
public ParentDataImpl(KidShiftApiService kidshiftApiService, UserSettings userSettings, KSLogger logger, KSActions ksActions) {
this.kidshiftApiService = kidshiftApiService;
this.userSettings = userSettings;
this.logger = logger;
this.ksActions = ksActions;
logger.setTag("ParentData");
}
// 一旦キャッシュを返して, その後非同期でAPIから取得更新があればコールバックで通知
@Override
public CompletableFuture<ParentModel> getParent(ParentModelCallback callback) {
// Start thread to fetch parent info
new Thread(() -> {
logger.info("Fetching parent info...");
ParentModel refreshedParent = ksActions.syncParent().join();
if (refreshedParent == null) {
callback.onFailed("Failed to fetch parent info");
} else {
// Workaround, TODO: Compare with existing parent
callback.onUpdated(refreshedParent);
}
}).start();
return CompletableFuture.supplyAsync(() -> userSettings.getCache().getParent());
}
@Override
public void updateParent(ParentModel parent) {
}
}

View File

@ -2,6 +2,8 @@ package one.nem.kidshift.data.impl;
import com.github.javafaker.Faker;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.RewardData;
@ -22,10 +24,7 @@ public class RewardDataDummyImpl implements RewardData {
}
@Override
public Integer getTotalReward() {
// logger.info("getTotalReward called");
Integer reward = faker.number().numberBetween(0, 10000);
// logger.info("Returning reward: " + reward);
return reward;
public CompletableFuture<Integer> getTotalReward() {
return CompletableFuture.supplyAsync(() -> faker.number().numberBetween(0, 1000));
}
}

View File

@ -1,78 +0,0 @@
package one.nem.kidshift.data.impl;
import com.github.javafaker.Faker;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.inject.Inject;
import one.nem.kidshift.data.TaskData;
import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.model.tasks.condition.TaskConditionBaseModel;
import one.nem.kidshift.model.tasks.condition.TaskConditionNoneModel;
import one.nem.kidshift.utils.KSLogger;
public class TaskDataDummyImpl implements TaskData {
private Faker faker;
@Inject
KSLogger logger;
@Inject
public TaskDataDummyImpl() {
faker = new Faker();
// logger.setTag("TaskDataDummyImpl");
}
@Override
public List<TaskItemModel> getTasks() {
// logger.info("getTotalReward called");
List<TaskItemModel> tasks = new ArrayList<>();
int totalTasks = faker.number().numberBetween(1, 15);
// logger.info("Returning total tasks: " + totalTasks);
for (int i = 0; i < totalTasks; i++) {
tasks.add(new TaskItemModel(
UUID.randomUUID().toString(),
faker.lorem().sentence(), UUID.randomUUID().toString(),
new TaskConditionNoneModel(),
faker.number().numberBetween(1, 1000)));
}
// logger.info("Returning tasks: " + tasks);
return tasks;
}
@Override
public void addTask(TaskItemModel task) {
logger.info("addTask called");
logger.info("Task: " + task);
}
@Override
public void removeTask(String taskId) {
logger.info("removeTask called");
logger.info("Task ID: " + taskId);
}
@Override
public void updateTask(TaskItemModel task) {
logger.info("updateTask called");
logger.info("Task: " + task);
}
@Override
public TaskItemModel getTask(String taskId) {
List<TaskItemModel> tasks = getTasks();
// return random task
return tasks.get(faker.number().numberBetween(0, tasks.size()));
}
@Override
public void recordTaskCompletion(String taskId, String childId) {
logger.info("recordTaskCompletion called");
logger.info("Task ID: " + taskId);
logger.info("Child ID: " + childId);
}
}

View File

@ -0,0 +1,68 @@
package one.nem.kidshift.data.impl;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.inject.Inject;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.TaskData;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.model.tasks.TaskItemModel;
public class TaskDataImpl implements TaskData {
KSActions ksActions;
@Inject
public TaskDataImpl(KSActions ksActions) {
this.ksActions = ksActions;
}
@Override
public CompletableFuture<List<TaskItemModel>> getTasks() {
return CompletableFuture.supplyAsync(() -> {
TaskListResponse data = ksActions.syncTasks().join();
return data.getList().stream().map(task -> {
// Convert TaskItemModel
TaskItemModel model = new TaskItemModel();
model.setInternalId(task.getId());
model.setDisplayName(task.getName());
model.setReward(task.getReward());
return model;
}).collect(Collectors.toList());
});
}
@Override
public CompletableFuture<List<TaskItemModel>> getTasks(String childId) {
return null;
}
@Override
public void addTask(TaskItemModel task) {
}
@Override
public void removeTask(String taskId) {
}
@Override
public void updateTask(TaskItemModel task) {
}
@Override
public CompletableFuture<TaskItemModel> getTask(String taskId) {
return CompletableFuture.completedFuture(null);
}
@Override
public void recordTaskCompletion(String taskId, String childId) {
}
}

View File

@ -1,20 +0,0 @@
package one.nem.kidshift.data.impl;
import android.graphics.Color;
import one.nem.kidshift.data.UserSettings;
public class UserSettingsDummyImpl implements UserSettings {
class Task implements UserSettings.Task {
@Override
public int getDefaultIconColor() {
return Color.parseColor("#FF0000");
}
@Override
public String getDefaultIconEmoji() {
return "🤔";
}
}
}

View File

@ -0,0 +1,219 @@
package one.nem.kidshift.data.impl;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import javax.inject.Inject;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.utils.SharedPrefUtils;
import one.nem.kidshift.utils.factory.SharedPrefUtilsFactory;
public class UserSettingsImpl implements UserSettings {
SharedPrefUtilsFactory sharedPrefUtilsFactory;
@Inject
public UserSettingsImpl(SharedPrefUtilsFactory sharedPrefUtilsFactory) {
this.sharedPrefUtilsFactory = sharedPrefUtilsFactory;
}
@Override
public ApiSetting getApiSetting() {
return new ApiSettingImpl();
}
@Override
public TaskSetting getTaskSetting() {
return new TaskSettingImpl();
}
@Override
public AppCommonSetting getAppCommonSetting() {
return new AppCommonSettingImpl();
}
@Override
public SharedPrefCache getCache() {
return new SharedPrefCacheImpl();
}
public class SharedPrefCacheImpl implements UserSettings.SharedPrefCache {
transient
SharedPrefUtils sharedPrefUtils;
ParentModel parent;
SharedPrefCacheImpl() {
sharedPrefUtils = sharedPrefUtilsFactory.create("user_settings");
SharedPrefCacheImpl sharedPrefCache = sharedPrefUtils.getObject("shared_pref_cache", SharedPrefCacheImpl.class);
if (sharedPrefCache != null) {
parent = sharedPrefCache.getParent();
} else {
parent = null;
}
}
private void save() {
sharedPrefUtils.saveObject("shared_pref_cache", this);
}
@Override
public ParentModel getParent() {
return parent;
}
@Override
public void setParent(ParentModel parent) {
this.parent = parent;
save();
}
}
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 {
transient
SharedPrefUtils sharedPrefUtils;
String apiBaseUrl;
ApiSettingImpl() {
sharedPrefUtils = sharedPrefUtilsFactory.create("user_settings");
ApiSettingImpl apiSetting = sharedPrefUtils.getObject("api_setting", ApiSettingImpl.class);
// TODO: リフレクションつかって一括でやる(プロパティ数があまりにも増えるなら?), 三項演算子やめる?, デフォルト値の設定方法を改善する
if (apiSetting != null) {
apiBaseUrl = apiSetting.apiBaseUrl.isEmpty() ? "https://kidshift-beta.nem.one/" : apiSetting.apiBaseUrl;
} else {
apiBaseUrl = "https://kidshift-beta.nem.one/";
}
}
private void save() {
sharedPrefUtils.saveObject("api_setting", this);
}
@Override
public String getApiBaseUrl() {
return apiBaseUrl;
}
@Override
public void setApiBaseUrl(String url) {
apiBaseUrl = url;
save();
}
}
public class TaskSettingImpl implements UserSettings.TaskSetting {
transient
SharedPrefUtils sharedPrefUtils;
int defaultIconColor;
String defaultIconEmoji;
TaskSettingImpl() {
sharedPrefUtils = sharedPrefUtilsFactory.create("user_settings");
TaskSettingImpl taskSetting = sharedPrefUtils.getObject("task_setting", TaskSettingImpl.class);
if (taskSetting != null) {
defaultIconColor = taskSetting.getDefaultIconColor() == 0 ? 0 : taskSetting.getDefaultIconColor();
defaultIconEmoji = taskSetting.getDefaultIconEmoji().isEmpty() ? "" : taskSetting.getDefaultIconEmoji();
} else {
defaultIconColor = 0;
defaultIconEmoji = "";
}
}
private void save() {
sharedPrefUtils.saveObject("task_setting", this);
}
@Override
public int getDefaultIconColor() {
return defaultIconColor;
}
@Override
public void setDefaultIconColor(int color) {
defaultIconColor = color;
save();
}
@Override
public String getDefaultIconEmoji() {
return defaultIconEmoji;
}
@Override
public void setDefaultIconEmoji(String emoji) {
defaultIconEmoji = emoji;
save();
}
}
}

View File

@ -1,16 +0,0 @@
package one.nem.kidshift.data.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.impl.ChildDataDummyImpl;
@Module
@InstallIn(FragmentComponent.class)
abstract public class ChildDataDummyModule {
@Binds
public abstract ChildData bindChildData(ChildDataDummyImpl childDataDummyImpl);
}

View File

@ -0,0 +1,16 @@
package one.nem.kidshift.data.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.impl.ChildDataImpl;
@Module
@InstallIn(SingletonComponent.class)
public abstract class ChildDataModule {
@Binds
abstract ChildData provideChildData(ChildDataImpl childDataImpl);
}

View File

@ -0,0 +1,16 @@
package one.nem.kidshift.data.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.impl.KSActionsImpl;
@Module
@InstallIn(SingletonComponent.class)
public abstract class KSActionsModule {
@Binds
public abstract KSActions bindKSActions(KSActionsImpl impl);
}

View File

@ -5,12 +5,12 @@ import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.data.impl.ParentDataDummyImpl;
import one.nem.kidshift.data.impl.ParentDataImpl;
@Module
@InstallIn(FragmentComponent.class)
abstract public class ParentDataDummyModule {
public abstract class ParentDataModule {
@Binds
public abstract ParentData bindParentData(ParentDataDummyImpl parentDataDummyImpl);
public abstract ParentData bindParentData(ParentDataImpl parentDataImpl);
}

View File

@ -5,12 +5,12 @@ import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.TaskData;
import one.nem.kidshift.data.impl.TaskDataDummyImpl;
import one.nem.kidshift.data.impl.TaskDataImpl;
@Module
@InstallIn(FragmentComponent.class)
abstract public class TaskDataDummyModule {
public abstract class TaskDataModule {
@Binds
public abstract TaskData bindTaskData(TaskDataDummyImpl taskDataDummyImpl);
public abstract TaskData bindTaskData(TaskDataImpl taskDataImpl);
}

View File

@ -1,16 +0,0 @@
package one.nem.kidshift.data.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.impl.UserSettingsDummyImpl;
@Module
@InstallIn(FragmentComponent.class)
abstract public class UserSettingsDummyModule {
@Binds
abstract UserSettings bindUserSettings(UserSettingsDummyImpl userSettingsDummyImpl);
}

View File

@ -0,0 +1,15 @@
package one.nem.kidshift.data.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.impl.UserSettingsImpl;
@Module
@InstallIn(SingletonComponent.class)
public abstract class UserSettingsModule {
@Binds
public abstract UserSettings bindUserSettings(UserSettingsImpl userSettingsImpl);
}

View File

@ -0,0 +1,119 @@
package one.nem.kidshift.data.retrofit;
import one.nem.kidshift.data.retrofit.interceptor.AuthorizationInterceptor;
import one.nem.kidshift.data.retrofit.model.child.ChildAddRequest;
import one.nem.kidshift.data.retrofit.model.child.ChildListResponse;
import one.nem.kidshift.data.retrofit.model.child.ChildResponse;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentLoginResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskAddRequest;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
public interface KidShiftApiService {
// Parent APIs
// Auth
/**
* 保護者ログイン処理
* @param request ParentLoginRequest
* @return ParentLoginResponse
*/
@POST("/parent/auth/login")
Call<ParentLoginResponse> parentLogin(@Body ParentLoginRequest request);
/**
* 保護者アカウント情報取得処理
* @return ParentInfoResponse
*/
@GET("/parent/account")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ParentInfoResponse> getParentInfo();
// Task APIs
/**
* タスク一覧取得
* @return TaskListResponse
*/
@GET("/parent/task")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskListResponse> getTasks();
/**
* タスク追加
* @param request TaskAddRequest
* @return TaskResponse
*/
@POST("/parent/task")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> addTask(@Body TaskAddRequest request);
/**
* タスク更新
* @param request TaskAddRequest
* @param id タスクID
* @return TaskResponse
*/
@PUT("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> updateTask(@Body TaskAddRequest request, @Path("id") String id);
/**
* タスク削除
* @param id タスクID
* @return Void
*/
@DELETE("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> removeTask(@Path("id") String id); // TODO-rca: OK responseをパース
/**
* タスク詳細取得
* @param id タスクID
* @return TaskResponse
*/
@GET("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> getTask(@Path("id") String id);
/**
* タスク完了処理
* @param id タスクID
* @return Void
*/
@POST("/parent/task/{id}/complete")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> completeTask(@Path("id") String id); // TODO-rca: OK responseをパース
// Child APIs
/**
* 子供一覧取得
* @return ChildListResponse
*/
@GET("/parent/child")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildListResponse> getChildList();
/**
* 子供追加
* @param request ChildAddRequest
* @return ChildResponse
*/
@POST("/parent/child")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildResponse> addChild(@Body ChildAddRequest request);
}

View File

@ -0,0 +1,60 @@
package one.nem.kidshift.data.retrofit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import okhttp3.OkHttpClient;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.interceptor.AuthorizationInterceptor;
import one.nem.kidshift.utils.KSLogger;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@Module
@InstallIn(SingletonComponent.class)
public class KidShiftApiServiceModule {
@Inject
KSLogger logger;
@Provides
@Singleton
public AuthorizationInterceptor provideAuthorizationInterceptor(UserSettings userSettings, KSLogger logger) {
return new AuthorizationInterceptor(userSettings, logger);
}
// Gson
@Provides
@Singleton
public Gson provideGson() {
return new GsonBuilder()
.create();
}
@Provides
@Singleton
public OkHttpClient provideOkHttpClient(AuthorizationInterceptor authorizationInterceptor) {
return new OkHttpClient.Builder()
.addInterceptor(authorizationInterceptor)
.build();
}
@Provides
@Singleton
public KidShiftApiService provideKidShiftApiService(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
// .baseUrl(userSettings.getApiSetting().getApiBaseUrl())
.baseUrl("https://kidshift-beta.nem.one/")
.addConverterFactory(GsonConverterFactory.create(provideGson()))
.client(okHttpClient)
.build()
.create(KidShiftApiService.class);
}
}

View File

@ -0,0 +1,52 @@
package one.nem.kidshift.data.retrofit.interceptor;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.IOException;
import javax.inject.Inject;
import okhttp3.Interceptor;
import okhttp3.Response;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.utils.KSLogger;
public class AuthorizationInterceptor implements Interceptor {
private static final String HEADER_NAME = "Authorization";
private static final String HEADER_VALUE = "VALUE";
public static final String HEADER_PLACEHOLDER = HEADER_NAME + ": " + HEADER_VALUE;
private final UserSettings userSettings;
private final KSLogger logger;
public AuthorizationInterceptor(UserSettings userSettings, KSLogger logger) {
this.userSettings = userSettings;
this.logger = logger;
logger.setTag("Auth_Interceptor");
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
logger.debug("intercept");
try {
if (chain.request().header(HEADER_NAME) == null) {
logger.debug("Authorization header is null");
return chain.proceed(chain.request());
}
if (!HEADER_VALUE.equals(chain.request().header(HEADER_NAME))) {
logger.debug("Authorization header is invalid");
return chain.proceed(chain.request());
}
return chain.proceed(chain.request().newBuilder()
.header(HEADER_NAME, "Barer " + userSettings.getAppCommonSetting().getAccessToken())
.build());
} catch (Exception e) {
return chain.proceed(chain.request());
}
}
}

View File

@ -0,0 +1,23 @@
package one.nem.kidshift.data.retrofit.model.child;
// Request to add a child
public class ChildAddRequest {
private String name;
// Constructor
public ChildAddRequest(String name) {
this.name = name;
}
public ChildAddRequest() {
}
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,33 @@
package one.nem.kidshift.data.retrofit.model.child;
// Base class for children
public class ChildBaseItem {
private String id;
private String name;
// Constructor
public ChildBaseItem(String id, String name) {
this.id = id;
this.name = name;
}
public ChildBaseItem() {
}
// Getters and setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,36 @@
package one.nem.kidshift.data.retrofit.model.child;
import java.util.Date;
// Response for detailed information about a child
public class ChildDetailsResponse extends ChildBaseItem {
private Date createdAt;
private String homeGroupId;
// Constructor
public ChildDetailsResponse(String id, String name, Date createdAt, String homeGroupId) {
super(id, name);
this.createdAt = createdAt;
this.homeGroupId = homeGroupId;
}
public ChildDetailsResponse() {
}
// Getters and setters
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public String getHomeGroupId() {
return homeGroupId;
}
public void setHomeGroupId(String homeGroupId) {
this.homeGroupId = homeGroupId;
}
}

View File

@ -0,0 +1,25 @@
package one.nem.kidshift.data.retrofit.model.child;
import java.util.List;
// Response for a list of children
public class ChildListResponse {
private List<ChildResponse> list;
// Constructor
public ChildListResponse(List<ChildResponse> list) {
this.list = list;
}
public ChildListResponse() {
}
// Getters and setters
public List<ChildResponse> getList() {
return list;
}
public void setList(List<ChildResponse> list) {
this.list = list;
}
}

View File

@ -0,0 +1,4 @@
package one.nem.kidshift.data.retrofit.model.child;
public class ChildRequest extends ChildBaseItem {
}

View File

@ -0,0 +1,4 @@
package one.nem.kidshift.data.retrofit.model.child;
public class ChildResponse extends ChildBaseItem {
}

View File

@ -0,0 +1,38 @@
package one.nem.kidshift.data.retrofit.model.parent;
public class ParentInfoResponse {
private String id;
private String email;
private String displayName;
public ParentInfoResponse(String id, String email, String displayName) {
this.id = id;
this.email = email;
this.displayName = displayName;
}
public String getId() {
return id;
}
public String getEmail() {
return email;
}
public String getDisplayName() {
return displayName;
}
public void setId(String id) {
this.id = id;
}
public void setEmail(String email) {
this.email = email;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}

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

@ -0,0 +1,39 @@
package one.nem.kidshift.data.retrofit.model.task;
import java.util.List;
public class TaskAddRequest extends TaskBaseItem {
private List<String> attachedChildren;
// コンストラクタ
// 全プロパティ
public TaskAddRequest(String name, String iconEmoji, String bgColor, int reward, List<String> attachedChildren) {
super(name, iconEmoji, bgColor, reward);
this.attachedChildren = attachedChildren;
}
// ID, Optionalなフィールドなし (登録時など)
public TaskAddRequest(String name, int reward, List<String> attachedChildren) {
super(name, reward);
this.attachedChildren = attachedChildren;
}
//
public TaskAddRequest() {
}
// baseItemを指定して拡張
public TaskAddRequest(TaskBaseItem taskBaseItem, List<String> attachedChildren) {
super(taskBaseItem.getName(), taskBaseItem.getIconEmoji(), taskBaseItem.getBgColor(), taskBaseItem.getReward());
this.attachedChildren = attachedChildren;
}
// Getters and setters
public List<String> getAttachedChildren() {
return attachedChildren;
}
public void setAttachedChildren(List<String> attachedChildren) {
this.attachedChildren = attachedChildren;
}
}

View File

@ -0,0 +1,85 @@
package one.nem.kidshift.data.retrofit.model.task;
public class TaskBaseItem {
private String id;
private String name;
private String iconEmoji; // Optional
private String bgColor; // Optional
private int reward;
// コンストラクタ
// 全プロパティ
public TaskBaseItem(String id, String name, String iconEmoji, String bgColor, int reward) {
this.id = id;
this.name = name;
this.iconEmoji = iconEmoji;
this.bgColor = bgColor;
this.reward = reward;
}
// IDなし (登録時など)
public TaskBaseItem(String name, String iconEmoji, String bgColor, int reward) {
this.name = name;
this.iconEmoji = iconEmoji;
this.bgColor = bgColor;
this.reward = reward;
}
// Optionalなフィールドなし
public TaskBaseItem(String id, String name, int reward) {
this.id = id;
this.name = name;
this.reward = reward;
}
// ID, Optionalなフィールドなし (登録時など)
public TaskBaseItem(String name, int reward) {
this.name = name;
this.reward = reward;
}
//
public TaskBaseItem() {
}
// Getters and setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIconEmoji() {
return iconEmoji;
}
public void setIconEmoji(String iconEmoji) {
this.iconEmoji = iconEmoji;
}
public String getBgColor() {
return bgColor;
}
public void setBgColor(String bgColor) {
this.bgColor = bgColor;
}
public int getReward() {
return reward;
}
public void setReward(int reward) {
this.reward = reward;
}
}

View File

@ -0,0 +1,26 @@
package one.nem.kidshift.data.retrofit.model.task;
import java.util.List;
public class TaskListResponse {
private List<TaskBaseItem> list;
// コンストラクタ
// 全プロパティ
public TaskListResponse(List<TaskBaseItem> list) {
this.list = list;
}
//
public TaskListResponse() {
}
// Getters and setters
public List<TaskBaseItem> getList() {
return list;
}
public void setList(List<TaskBaseItem> list) {
this.list = list;
}
}

View File

@ -0,0 +1,39 @@
package one.nem.kidshift.data.retrofit.model.task;
import java.util.List;
public class TaskResponse extends TaskBaseItem {
private List<String> attachedChildren;
// コンストラクタ
// 全プロパティ
public TaskResponse(String id, String name, String iconEmoji, String bgColor, int reward, List<String> attachedChildren) {
super(id, name, iconEmoji, bgColor, reward);
this.attachedChildren = attachedChildren;
}
// 必須プロパティ
public TaskResponse(String id, String name, int reward, List<String> attachedChildren) {
super(id, name, reward);
this.attachedChildren = attachedChildren;
}
//
public TaskResponse() {
}
// baseItemを指定して拡張
public TaskResponse(TaskBaseItem taskBaseItem, List<String> attachedChildren) {
super(taskBaseItem.getId(), taskBaseItem.getName(), taskBaseItem.getIconEmoji(), taskBaseItem.getBgColor(), taskBaseItem.getReward());
this.attachedChildren = attachedChildren;
}
// Getters and setters
public List<String> getAttachedChildren() {
return attachedChildren;
}
public void setAttachedChildren(List<String> attachedChildren) {
this.attachedChildren = attachedChildren;
}
}

View File

@ -0,0 +1,22 @@
package one.nem.kidshift.data.room;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import one.nem.kidshift.data.room.dao.ChildCacheDao;
import one.nem.kidshift.data.room.dao.TaskCacheDao;
import one.nem.kidshift.data.room.dao.TaskChildLinkageDao;
import one.nem.kidshift.data.room.entity.ChildCacheEntity;
import one.nem.kidshift.data.room.entity.TaskCacheEntity;
import one.nem.kidshift.data.room.entity.TaskChildLinkageEntity;
@Database(entities = {ChildCacheEntity.class, TaskCacheEntity.class, TaskChildLinkageEntity.class}, version = 1)
public abstract class KidShiftDatabase extends RoomDatabase {
public abstract ChildCacheDao childCacheDao();
public abstract TaskCacheDao taskCacheDao();
public abstract TaskChildLinkageDao taskChildLinkageDao();
}

View File

@ -0,0 +1,26 @@
package one.nem.kidshift.data.room;
import android.content.Context;
import androidx.room.Room;
import dagger.Module;
import dagger.Provides;
import dagger.hilt.InstallIn;
import dagger.hilt.android.qualifiers.ApplicationContext;
import dagger.hilt.components.SingletonComponent;
@Module
@InstallIn(SingletonComponent.class)
public class KidShiftDatabaseModule {
@Provides
public static KidShiftDatabase provideKidShiftDatabase(@ApplicationContext Context context) {
return Room.databaseBuilder(context,
KidShiftDatabase.class,
"cache.db")
.fallbackToDestructiveMigration() // DEBUG_ONLY Migrationがない場合に破壊的なマイグレーションを行うことを許可
.fallbackToDestructiveMigrationOnDowngrade() // DEBUG_ONLY ダウングレード時に破壊的なマイグレーションを行うことを許可
.build();
}
}

View File

@ -0,0 +1,7 @@
package one.nem.kidshift.data.room.dao;
import androidx.room.Dao;
@Dao
public interface ChildCacheDao {
}

View File

@ -0,0 +1,7 @@
package one.nem.kidshift.data.room.dao;
import androidx.room.Dao;
@Dao
public interface TaskCacheDao {
}

View File

@ -0,0 +1,7 @@
package one.nem.kidshift.data.room.dao;
import androidx.room.Dao;
@Dao
public interface TaskChildLinkageDao {
}

View File

@ -0,0 +1,18 @@
package one.nem.kidshift.data.room.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "child_cache")
public class ChildCacheEntity {
@PrimaryKey
@ColumnInfo(name = "id")
@NonNull
public String Id;
@ColumnInfo(name = "display_name")
public String displayName;
}

View File

@ -0,0 +1,24 @@
package one.nem.kidshift.data.room.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "task_cache")
public class TaskCacheEntity {
@PrimaryKey
@ColumnInfo(name = "id")
@NonNull
public String Id;
@ColumnInfo(name = "display_name")
public String displayName;
@ColumnInfo(name = "icon_emoji")
public String iconEmoji;
@ColumnInfo(name = "reward")
public int reward;
}

View File

@ -0,0 +1,22 @@
package one.nem.kidshift.data.room.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "task_child_linkage")
public class TaskChildLinkageEntity {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
@NonNull
public int id;
@ColumnInfo(name = "task_id")
public String taskId;
@ColumnInfo(name = "child_id")
public String childId;
}

View File

@ -1,5 +1,7 @@
package one.nem.kidshift.feature.debug;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@ -10,6 +12,7 @@ import dagger.hilt.EntryPoint;
import dagger.hilt.InstallIn;
import dagger.hilt.android.AndroidEntryPoint;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.models.LogModel;
@ -20,12 +23,16 @@ public class DebugCommandProcessor {
KSLogger ksLogger;
FeatureFlag featureFlag;
UserSettings userSettings;
public DebugCommandProcessor(
KSLogger ksLogger,
FeatureFlag featureFlag
FeatureFlag featureFlag,
UserSettings userSettings
) {
this.ksLogger = ksLogger;
this.featureFlag = featureFlag;
this.userSettings = userSettings;
}
public String execute(String command) {
@ -52,11 +59,64 @@ public class DebugCommandProcessor {
return executeLog(commandArray);
case "flag":
return executeFlag(commandArray);
case "setting":
return executeSetting(commandArray);
default:
throw new InvalidCommandException();
}
}
// TODO: リフレクション処理切り出し, 複数の引数に対応, String以外の引数に対応
private String executeSetting(String[] commandArray) {
commandArray = shiftArray(commandArray);
Class<?> settingClazz;
switch (commandArray[0]) {
case "get":
commandArray = shiftArray(commandArray);
// リフレクションで取得
try {
// userSettingsのgetterでsettingクラスを取得
Method method = userSettings.getClass().getMethod("get" + commandArray[0]);
Object setting = method.invoke(userSettings);
//settingクラスのgetterで値を取得
Method settingMethod = setting.getClass().getMethod("get" + commandArray[1]);
return settingMethod.invoke(setting).toString();
} catch (NoSuchMethodException e) {
return "Method" + commandArray[0] + " not found \n" + e.getMessage();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
return "Method" + commandArray[0] + " not accessible \n" + e.getMessage();
} catch (Exception e) {
return "Something went wrong! \n" + e.getMessage();
}
case "set":
commandArray = shiftArray(commandArray);
// リフレクションで取得
try {
// userSettingsのgetterでsettingクラスを取得
Method method = userSettings.getClass().getMethod("get" + commandArray[0]);
Object setting = method.invoke(userSettings);
//settingクラスのsetterで値を設定
Method settingMethod = setting.getClass().getMethod("set" + commandArray[1], String.class); // TODO: String以外の型に対応
settingMethod.invoke(setting, commandArray[2]);
return "Setting set!";
} catch (NoSuchMethodException e) {
return "Method" + commandArray[0] + " not found \n" + e.getMessage();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
return "Method" + commandArray[0] + " not accessible \n" + e.getMessage();
} catch (Exception e) {
return "Something went wrong! \n" + e.getMessage();
}
default:
return "TODO";
}
}
private String executeLog(String[] commandArray) {
commandArray = shiftArray(commandArray);
switch (commandArray[0]) {

View File

@ -16,6 +16,7 @@ import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.feature.debug.adapter.DebugCommandListItemAdapter;
import one.nem.kidshift.feature.debug.model.DebugCommandItemModel;
import one.nem.kidshift.utils.FeatureFlag;
@ -29,6 +30,9 @@ public class DebugDebugConsoleFragment extends Fragment {
@Inject
FeatureFlag featureFlag;
@Inject
UserSettings userSettings;
private final List<DebugCommandItemModel> debugCommandItemModels = new ArrayList<>();
DebugCommandListItemAdapter debugCommandItemAdapter;
@ -59,7 +63,7 @@ public class DebugDebugConsoleFragment extends Fragment {
TextView debugCommandInput = view.findViewById(R.id.debugCommandEditText);
view.findViewById(R.id.debugCommandExecuteButton).setOnClickListener(v -> {
DebugCommandProcessor debugCommandProcessor = new DebugCommandProcessor(
ksLogger, featureFlag);
ksLogger, featureFlag, userSettings);
debugCommandItemModels.add(
new DebugCommandItemModel(
debugCommandInput.getText().toString(),

View File

@ -1,62 +0,0 @@
package one.nem.kidshift.feature.debug;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.TaskData;
@AndroidEntryPoint
public class DebugMockTestFragment extends Fragment {
@Inject
TaskData taskData;
@Inject
RewardData rewardData;
public DebugMockTestFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_debug_mock_test, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// TaskData
TextView taskDataStatus = view.findViewById(R.id.taskData_mockedStatusTextView);
taskDataStatus.setText("isMocked: true"); // TODO: 固定値やめる
TextView taskDataResult = view.findViewById(R.id.taskData_resultTextView);
view.findViewById(R.id.taskData_getTasksButton).setOnClickListener(v -> {
taskDataResult.setText(taskData.getTasks().stream().map(Object::toString).reduce("", (a, b) -> a + b + "\n"));
});
// RewardData
TextView rewardDataStatus = view.findViewById(R.id.rewardData_mockedStatusTextView);
rewardDataStatus.setText("isMocked: true"); // TODO: 固定値やめる
TextView rewardDataResult = view.findViewById(R.id.rewardData_resultTextView);
view.findViewById(R.id.rewardData_getTotalRewardButton).setOnClickListener(v -> {
rewardDataResult.setText(rewardData.getTotalReward().toString());
});
}
}

View File

@ -31,7 +31,6 @@ public class DebugTopMenuFragment extends Fragment {
recyclerView.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(getContext()));
List<DebugMenuListItemModel> debugMenuListItems = new ArrayList<>();
debugMenuListItems.add(new DebugMenuListItemModel("Data mock tester", "データモジュールの取得処理のモックをテストします", R.id.action_debugTopMenuFragment_to_debugMockTestFragment, true));
debugMenuListItems.add(new DebugMenuListItemModel("Debug console", "デバッグコマンドを実行します", R.id.action_debugTopMenuFragment_to_debugDebugConsoleFragment, true));
debugMenuListItems.add(new DebugMenuListItemModel("Temp login", "仮置きログイン画面を表示", R.id.action_debugTopMenuFragment_to_debugTempLoginFragment, true));
debugMenuListItems.add(new DebugMenuListItemModel("Temp register", "仮置き登録画面を表示", R.id.action_debugTopMenuFragment_to_debugTempRegisterFragment, true));

View File

@ -1,119 +0,0 @@
<?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=".DebugMockTestFragment">
<!-- TODO: Update blank fragment layout -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
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" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TaskData"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/taskData_mockedStatusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="isMocked:" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/taskData_getTasksButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="getTasks()"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<Button
android:id="@+id/taskData_placeholderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="-----" />
</LinearLayout>
<TextView
android:id="@+id/taskData_resultTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="result..." />
</LinearLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="16dp"
android:background="?android:attr/listDivider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="RewardData"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/rewardData_mockedStatusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="isMocked:" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/rewardData_getTotalRewardButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="getTotalReward()"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
<TextView
android:id="@+id/rewardData_resultTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="result..." />
</LinearLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,9 +10,6 @@
android:name="one.nem.kidshift.feature.debug.DebugTopMenuFragment"
android:label="fragment_debug_top_menu"
tools:layout="@layout/fragment_debug_top_menu" >
<action
android:id="@+id/action_debugTopMenuFragment_to_debugMockTestFragment"
app:destination="@id/debugMockTestFragment" />
<action
android:id="@+id/action_debugTopMenuFragment_to_debugDebugConsoleFragment"
app:destination="@id/debugDebugConsoleFragment" />
@ -23,11 +20,6 @@
android:id="@+id/action_debugTopMenuFragment_to_debugTempRegisterFragment"
app:destination="@id/debugTempRegisterFragment" />
</fragment>
<fragment
android:id="@+id/debugMockTestFragment"
android:name="one.nem.kidshift.feature.debug.DebugMockTestFragment"
android:label="fragment_debug_mock_test"
tools:layout="@layout/fragment_debug_mock_test" />
<fragment
android:id="@+id/debugDebugConsoleFragment"
android:name="one.nem.kidshift.feature.debug.DebugDebugConsoleFragment"

View File

@ -52,7 +52,7 @@ public class ParentMainFragment extends Fragment {
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
List<TaskItemModel> task = taskData.getTasks();
List<TaskItemModel> task = taskData.getTasks().join();
RecyclerView.Adapter mainAdapter = new ParentAdapter(task);
recyclerView.setAdapter(mainAdapter);

View File

@ -17,6 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
@ -25,6 +26,7 @@ import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback;
/**
* A simple {@link Fragment} subclass.
@ -86,8 +88,41 @@ public class SettingMainFragment extends Fragment {
Bundle savedInstanceState) {
// Inflate the layout for this fragment
//親の名前アドレス表示
ParentModel parent = parentData.getParent("poiuytrew");
CompletableFuture<ParentModel> completableFuture = parentData.getParent(new ParentModelCallback() {
@Override
public void onUnchanged() {
// TODO
}
@Override
public void onUpdated(ParentModel parent) {
// TODO
}
@Override
public void onFailed(String message) {
// TODO
}
});
/*
TODO:
- コールバックの処理を実装
- 結果に応じてRecyclerViewを更新する
- キャッシュ受け取りの時にjoinでUIスレッドをブロックしないように
- Placeholderの表示?
- エラーハンドリング
- onFailed時にそれを通知
*/
ParentModel parent = completableFuture.join();
if (parent == null) {
parent = new ParentModel(); // Workaround非ログインデバッグ用
parent.setDisplayName("親の名前");
parent.setEmail("親のアドレス");
}
@ -134,7 +169,7 @@ public class SettingMainFragment extends Fragment {
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
List<ChildModel> child = childData.getChildList();
List<ChildModel> child = childData.getChildList().join();
RecyclerView.Adapter mainAdapter = new SettingAdapter(child);
recyclerView.setAdapter(mainAdapter);

View File

@ -10,6 +10,8 @@ activity = "1.9.0"
constraintlayout = "2.1.4"
nav = "2.7.7"
swiperefreshlayout = "1.1.0"
retrofit = "2.11.0"
room = "2.5.0"
[libraries]
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
@ -28,6 +30,10 @@ navigation-fragment = { group="androidx.navigation", name="navigation-fragment",
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"}
swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }

View File

@ -25,6 +25,9 @@ public class ParentModel {
this.displayName = displayName;
}
public ParentModel() {
}
// Getter
public String getInternalId() {

View File

@ -0,0 +1,9 @@
package one.nem.kidshift.model.callback;
import one.nem.kidshift.model.ParentModel;
public interface ParentModelCallback {
void onUnchanged();
void onUpdated(ParentModel parent);
void onFailed(String message);
}

View File

@ -44,6 +44,10 @@ public class TaskItemModel {
this.reward = reward;
}
public TaskItemModel() {
}
// getter setter
public String getInternalId() {

View File

@ -1,7 +1,5 @@
package one.nem.kidshift.utils;
import android.content.SharedPreferences;
import java.util.List;
public interface SharedPrefUtils {