Compare commits

..

No commits in common. "main" and "improve/rollback" have entirely different histories.

169 changed files with 611 additions and 5724 deletions

View File

@ -13,11 +13,9 @@
<option value="$PROJECT_DIR$/data" />
<option value="$PROJECT_DIR$/feature" />
<option value="$PROJECT_DIR$/feature/child" />
<option value="$PROJECT_DIR$/feature/common" />
<option value="$PROJECT_DIR$/feature/debug" />
<option value="$PROJECT_DIR$/feature/parent" />
<option value="$PROJECT_DIR$/feature/setting" />
<option value="$PROJECT_DIR$/feature/wallet" />
<option value="$PROJECT_DIR$/model" />
<option value="$PROJECT_DIR$/shared" />
<option value="$PROJECT_DIR$/utils" />

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 グループワークチーム「シフトメイト」メンバー
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,38 +1,8 @@
# WIP
~~## メモ
## メモ
- リリース前(=提出前)には`DEBUG_ONLY`で検索してチェック(念のため)
## リリース前チェック
- DBの破壊的マイグレーションを許可するオプションを無効に~~
#### 補足
- カレンダーはCompactCalendarViewをそのまま使用する予定でしたがAndroidX環境で使用するとクラス重複でビルドできないためAndroidXに対応させるPRを取り込んだ物を専用Mavenリポジトリとして公開して使用しています
- https://github.com/r-ca/CompactCalendarView
## 補足
- 直前に大規模リファクタリングを始めていたため,未使用コード(ファイル)がいくつか残っています
- `:feature:setting`, `:feature:parent` は廃止されており, 現在は使用されていません
- `:feature:child` は子供管理画面のアクティビティのみ使用されています
- 親, 子供のタスク一覧画面はどちらも`:feature:common`の`CommonHomeFragment`を用いており, ナビゲーショングラフを切り替えることで表示モードを切り替えています
## 既知の問題
- 初回起動時, ウォレットの表示に失敗する場合がある
- 特定の操作を行った場合にナビゲーションが正常に動作しなくなる場合がある
- 特定の状況で子供モード時に追加ウィンドウが開けてしまう場合がある(APIの権限チェックではじかれるため, 実際に追加することは不可能)
- カレンダーの表示を切り替える際, RecyclerViewのアニメーションが一定範囲にしか反映されない
- お手伝い履歴がローカルキャッシュされておらず,毎回サーバーから全データを取得している
- ウォレット画面でPull-to-Refreshが動作しない
- ウォレット画面にて非UIスレッドでUI更新を行ってしまっている?
- オフライン時,ウォレットなど一部の画面でクラッシュする場合がある
- 資格情報が間違ったままログインできてしまう
- キャッシュとサーバーのマスターデータに差異があった場合, 再表示しないと表示に適応されない場合がある(コールバックの処理が適切に実装されていない画面がある)
## TODO
- 全体的なUX改善
- インメモリデータベースの活用(Related: 全体的なUX改善)
- ViewModelの本格導入
- タスクのアサイン機能への対応
- 親モードで子供画面を表示したとき, 親モードへの移動にロックをかけられるようにする
- 非DynamicColor機種で使用されるテーマの適用が中途半端なので完全に適用するように
- DBの破壊的マイグレーションを許可するオプションを無効に
-

View File

@ -35,7 +35,6 @@ dependencies {
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
implementation project(':model')
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
@ -45,8 +44,6 @@ dependencies {
implementation project(':feature:parent')
implementation project(':feature:child')
implementation project(':feature:setting')
implementation project(':feature:common')
implementation project(':feature:wallet')
implementation project(':data')

View File

@ -15,12 +15,6 @@
android:supportsRtl="true"
android:theme="@style/Theme.KidShift"
tools:targetApi="31">
<activity
android:name=".ChildLoginActivity"
android:exported="false" />
<activity
android:name=".RegisterActivity"
android:exported="false" />
<activity
android:name=".LoginActivity"
android:exported="false" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,154 +0,0 @@
package one.nem.kidshift;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
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.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.child.auth.ChildAuthRequest;
import one.nem.kidshift.data.retrofit.model.child.auth.ChildAuthResponse;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
@AndroidEntryPoint
public class ChildLoginActivity extends AppCompatActivity {
@Inject
UserSettings userSettings;
@Inject
KSLoggerFactory loggerFactory;
@Inject
KidShiftApiService kidShiftApiService;
private KSLogger logger;
private EditText loginCode1, loginCode2, loginCode3, loginCode4, loginCode5, loginCode6, loginCode7, loginCode8;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_child_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;
});
logger = loggerFactory.create("ChildLoginActivity");
// コードのフォーカスを自動で移動する
loginCode1 = findViewById(R.id.loginCode_1);
loginCode2 = findViewById(R.id.loginCode_2);
loginCode3 = findViewById(R.id.loginCode_3);
loginCode4 = findViewById(R.id.loginCode_4);
loginCode5 = findViewById(R.id.loginCode_5);
loginCode6 = findViewById(R.id.loginCode_6);
loginCode7 = findViewById(R.id.loginCode_7);
loginCode8 = findViewById(R.id.loginCode_8);
loginCode1.addTextChangedListener(new LoginCodeTextWatcher(loginCode1, loginCode2, null));
loginCode2.addTextChangedListener(new LoginCodeTextWatcher(loginCode2, loginCode3, loginCode1));
loginCode3.addTextChangedListener(new LoginCodeTextWatcher(loginCode3, loginCode4, loginCode2));
loginCode4.addTextChangedListener(new LoginCodeTextWatcher(loginCode4, loginCode5, loginCode3));
loginCode5.addTextChangedListener(new LoginCodeTextWatcher(loginCode5, loginCode6, loginCode4));
loginCode6.addTextChangedListener(new LoginCodeTextWatcher(loginCode6, loginCode7, loginCode5));
loginCode7.addTextChangedListener(new LoginCodeTextWatcher(loginCode7, loginCode8, loginCode6));
loginCode8.addTextChangedListener(new LoginCodeTextWatcher(loginCode8, null, loginCode7));
// ログインボタンを押したときの処理
findViewById(R.id.childLoginButton).setOnClickListener(v -> {
logger.debug("ログインボタンが押されました");
Call<ChildAuthResponse> call = kidShiftApiService.childLogin(new ChildAuthRequest(getLoginCode()));
CompletableFuture.runAsync(() -> {
try {
ChildAuthResponse childAuthResponse = call.execute().body();
if (childAuthResponse == null || childAuthResponse.getAccessToken() == null) {
// エラー処理
logger.error("ChildAuthResponseがnullまたはAccessTokenがnullです");
return;
}
UserSettings.AppCommonSetting appCommonSetting = userSettings.getAppCommonSetting();
appCommonSetting.setLoggedIn(true);
appCommonSetting.setAccessToken(childAuthResponse.getAccessToken());
appCommonSetting.setChildId(childAuthResponse.getChildId());
appCommonSetting.setChildMode(true);
} catch (Exception e) {
logger.error("リクエストに失敗しました");
Toast.makeText(this, "ログインに失敗しました", Toast.LENGTH_SHORT).show();
}
}).thenRun(() -> {
startActivity(new Intent(this, MainActivity.class));
});
});
}
private String getLoginCode() {
return loginCode1.getText().toString() +
loginCode2.getText().toString() +
loginCode3.getText().toString() +
loginCode4.getText().toString() +
loginCode5.getText().toString() +
loginCode6.getText().toString() +
loginCode7.getText().toString() +
loginCode8.getText().toString();
}
private static class LoginCodeTextWatcher implements TextWatcher, View.OnKeyListener {
private EditText currentView;
private final EditText nextView;
private final EditText previousView;
LoginCodeTextWatcher(EditText currentView, EditText nextView, EditText previousView) {
this.currentView = currentView;
this.nextView = nextView;
this.previousView = previousView;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override
public void afterTextChanged(Editable s) {
if (s.length() == 1 && nextView != null) {
nextView.requestFocus();
}
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { // TODO: バックスペースの処理
// EditText currentView = (EditText) v;
// if (currentView.getText().length() == 0 && previousView != null) {
// previousView.requestFocus();
// }
// }
// return false;
return true;
}
}
}

View File

@ -1,6 +1,5 @@
package one.nem.kidshift;
import android.content.Intent;
import android.os.Bundle;
import android.widget.EditText;
@ -18,11 +17,10 @@ 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.ParentAuthRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthResponse;
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 one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@ -38,9 +36,6 @@ public class LoginActivity extends AppCompatActivity {
@Inject
UserSettings userSettings;
@Inject
KidShiftApiService kidShiftApiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -54,45 +49,61 @@ public class LoginActivity extends AppCompatActivity {
logger = loggerFactory.create("LoginActivity");
EditText emailEditText = findViewById(R.id.parentLoginEmailEditText);
EditText passwordEditText = findViewById(R.id.parentLoginPasswordEditText);
// Retrofit init
KidShiftApiService apiService = new Retrofit.Builder()
.baseUrl("https://kidshift-beta.nem.one/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(KidShiftApiService.class);
findViewById(R.id.parentLoginButton).setOnClickListener(v -> {
String email = emailEditText.getText().toString(); // TODO: メールアドレスのバリデーション
String password = passwordEditText.getText().toString();
EditText emailEditText = findViewById(R.id.emailEditText);
EditText passwordEditText = findViewById(R.id.passwordEditText);
CompletableFuture.runAsync(() -> {
Call<ParentAuthResponse> call = kidShiftApiService.parentLogin(new ParentAuthRequest(email, password));
findViewById(R.id.loginButton).setOnClickListener(v -> {
CompletableFuture.supplyAsync(() -> {
try {
Response<ParentAuthResponse> response = call.execute();
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()) {
ParentAuthResponse parentAuthResponse = response.body();
if (parentAuthResponse == null || parentAuthResponse.getAccessToken() == null) {
// エラー処理
logger.error("ParentAuthResponseがnullまたはAccessTokenがnullです");
return;
}
userSettings.getAppCommonSetting().setLoggedIn(true);
userSettings.getAppCommonSetting().setAccessToken(parentAuthResponse.getAccessToken());
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("リクエストに失敗しました");
// エラー処理
logger.error("Login Failed");
try {
logger.debug("Response: " + response.errorBody().string());
} catch (IOException e) {
logger.error("IOException while reading error body");
}
} catch (Exception e) {
logger.error("リクエストに失敗しました: " + e.getMessage());
e.printStackTrace();
// ログイン失敗時の処理
}
}).thenRun(() -> {
startActivity(new Intent(this, MainActivity.class));
}).exceptionally(e -> {
logger.error("Exception occurred: " + e.getMessage());
return null;
});
});
findViewById(R.id.toRegisterButton).setOnClickListener(v -> {
startActivity(new Intent(this, RegisterActivity.class));
});
findViewById(R.id.toChildLoginButton).setOnClickListener(v -> {
startActivity(new Intent(this, ChildLoginActivity.class));
// for Debug
findViewById(R.id.loginButton).setOnLongClickListener(v -> {
// ログイン画面をバイパスしてメイン画面に遷移
finish();
return true;
});
}
}

View File

@ -1,75 +1,37 @@
package one.nem.kidshift;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.divider.MaterialDivider;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.feature.child.ChildManageMainActivity;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback;
import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
KSLoggerFactory ksLoggerFactory;
@Inject
FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject
FeatureFlag featureFlag;
@Inject
UserSettings userSettings;
@Inject
ParentData parentData;
@Inject
ChildData childData;
KSLoggerFactory loggerFactory;
private KSLogger logger;
private FloatingActionButton fab;
private Toolbar toolbar;
@Inject
UserSettings userSettings;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -82,52 +44,9 @@ public class MainActivity extends AppCompatActivity {
return insets;
});
logger = ksLoggerFactory.create("MainActivity");
logger = loggerFactory.create("MainActivity");
// Check logged in
if (userSettings.getAppCommonSetting().isLoggedIn()) {
logger.info("User is logged in!");
} else {
logger.info("User is not logged in!");
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolBarManager.setToolbar(toolbar);
DrawerLayout drawerLayout = findViewById(R.id.drawerLayout);
// アイテムが選択されたときの処理
NavigationView navigationView = findViewById(R.id.navigationView);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
logger.debug("Item selected: " + item.getItemId());
if (item.getItemId() == R.id.manage_child_account) {
Intent intent = new Intent(MainActivity.this, ChildManageMainActivity.class);
startActivity(intent);
return true;
} else if (item.getItemId() == R.id.show_debug_dialog) {
showDebugDialog();
return true;
} else if (item.getItemId() == R.id.show_account_dialog) {
showAccountDialog();
return true;
} else {
logger.warn("不明なアイテム: " + item.getItemId());
}
return false;
}
});
ActionBarDrawerToggle actionBarDrawerToggle =
new ActionBarDrawerToggle(
this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close);
drawerLayout.addDrawerListener(actionBarDrawerToggle);
actionBarDrawerToggle.syncState();
logger.info("MainActivity started!");
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav);
@ -143,27 +62,15 @@ public class MainActivity extends AppCompatActivity {
e.printStackTrace();
}
UserSettings.AppCommonSetting appCommonSetting = userSettings.getAppCommonSetting();
if (appCommonSetting.isChildMode()) {
logger.info("Child mode is enabled!");
// 保護者向けのナビゲーションを削除
bottomNavigationView.getMenu().removeItem(R.id.feature_common_parent_child_navigation);
bottomNavigationView.getMenu().removeItem(R.id.feature_common_parent_parent_navigation);
bottomNavigationView.getMenu().removeItem(R.id.feature_wallet_parent_navigation);
// startDestinationを変更
bottomNavigationView.setSelectedItemId(R.id.feature_common_child_child_navigation);
// manage_child_accountを削除
navigationView.getMenu().removeItem(R.id.manage_child_account);
// Check logged in
if (userSettings.getAppCommonSetting().isLoggedIn()) {
logger.info("User is logged in!");
} else {
logger.info("Child mode is disabled!");
bottomNavigationView.getMenu().removeItem(R.id.feature_common_child_child_navigation);
bottomNavigationView.getMenu().removeItem(R.id.feature_wallet_child_navigation);
}
logger.info("User is not logged in!");
fab = findViewById(R.id.mainFab);
fabManager.setFab(fab);
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
}
/**
@ -172,122 +79,4 @@ public class MainActivity extends AppCompatActivity {
private void startup() {
}
private void showAccountDialog() {
boolean isEditMode = false;
View view = getLayoutInflater().inflate(R.layout.user_info_dialog_layout, null);
if (userSettings.getAppCommonSetting().isChildMode()) {
childData.getChild(userSettings.getAppCommonSetting().getChildId()).thenAccept(childModel -> {
((TextView) view.findViewById(R.id.userNameTextView)).setText(childModel.getName());
logger.debug("ChildModel: " + childModel.getName());
((TextView) view.findViewById(R.id.emailTextView)).setText("子供ユーザーはメールアドレスを設定できません");
((Chip) view.findViewById(R.id.chip)).setText("子供/Child");
}).join();
} else {
parentData.getParentDirect().thenAccept(parentModel -> {
((TextView) view.findViewById(R.id.userNameTextView)).setText(parentModel.getName());
logger.debug("ParentModel: " + parentModel.getName() + ", " + parentModel.getEmail());
((TextView) view.findViewById(R.id.emailTextView)).setText(parentModel.getEmail());
((Chip) view.findViewById(R.id.chip)).setText("保護者/Parent");
}).join();
}
// Workaround
if (userSettings.getAppCommonSetting().isChildMode()) {
view.findViewById(R.id.userNameEditButton).setVisibility(View.GONE);
} else {
view.findViewById(R.id.userNameEditButton).setVisibility(View.VISIBLE);
}
view.findViewById(R.id.userNameEditButton).setOnClickListener(v -> {
EditText editText = new EditText(this);
editText.setText(((TextView) view.findViewById(R.id.userNameTextView)).getText());
editText.setHint("ユーザー名");
new MaterialAlertDialogBuilder(this)
.setTitle("ユーザー名の変更")
.setView(editText)
.setPositiveButton("OK", (dialog, which) -> {
((TextView) view.findViewById(R.id.userNameTextView)).setText(editText.getText());
if (userSettings.getAppCommonSetting().isChildMode()) {
// 子供モード
childData.getChild(userSettings.getAppCommonSetting().getChildId()).thenAccept(childModel -> {
childModel.setName(editText.getText().toString());
childData.updateChild(childModel);
});
} else {
// 保護者モード
parentData.getParentDirect().thenAccept(parentModel -> {
parentModel.setName(editText.getText().toString());
parentData.updateParent(parentModel);
});
}
})
.setNegativeButton("キャンセル", (dialog, which) -> {
// Do nothing
})
.show();
});
new MaterialAlertDialogBuilder(this)
.setTitle("アカウント情報")
.setView(view)
.setPositiveButton("OK", (dialog, which) -> {
// Do nothing
})
.show();
}
private void showDebugDialog() {
ScrollView scrollView = new ScrollView(this);
scrollView.setPadding(32, 16, 32, 16);
LinearLayout linearLayout = new LinearLayout(this);
TextView serverAddressTextView = new TextView(this);
serverAddressTextView.setText("サーバーアドレス: " + userSettings.getApiSetting().getApiBaseUrl());
serverAddressTextView.setTextSize(16);
TextView accessTokenTextView = new TextView(this);
accessTokenTextView.setText("アクセストークン: " + userSettings.getAppCommonSetting().getAccessToken());
accessTokenTextView.setTextSize(16);
TextView childModeTextView = new TextView(this);
childModeTextView.setText("子供モード: " + userSettings.getAppCommonSetting().isChildMode());
childModeTextView.setTextSize(16);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.addView(serverAddressTextView);
linearLayout.addView(createDivider(this));
linearLayout.addView(accessTokenTextView);
linearLayout.addView(createDivider(this));
linearLayout.addView(childModeTextView);
scrollView.addView(linearLayout);
new MaterialAlertDialogBuilder(this)
.setTitle("参考情報(評価用)")
.setView(scrollView)
.setPositiveButton("OK", (dialog, which) -> {
// Do nothing
})
.show();
}
private MaterialDivider createDivider(Context context) {
MaterialDivider divider = new MaterialDivider(context);
// Margin (48, 16, 48, 16)
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(48, 16, 48, 16);
divider.setLayoutParams(params);
return divider;
}
public Toolbar getToolbar() { // TODO: toolbarのインスタンス自体を取得するのではなくfabのように操作できるようにする
return toolbar;
}
}

View File

@ -1,97 +0,0 @@
package one.nem.kidshift;
import android.content.Intent;
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.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.ParentAuthRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthResponse;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
import retrofit2.Response;
@AndroidEntryPoint
public class RegisterActivity extends AppCompatActivity {
@Inject
KidShiftApiService kidShiftApiService;
@Inject
UserSettings userSettings;
@Inject
KSLoggerFactory loggerFactory;
private KSLogger logger;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_register);
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;
});
logger = loggerFactory.create("RegisterActivity");
EditText emailEditText = findViewById(R.id.parentRegisterEmailEditText); // TODO: メールアドレスのバリデーション
EditText passwordEditText = findViewById(R.id.parentRegisterPasswordEditText);
findViewById(R.id.parentRegisterButton).setOnClickListener(v -> {
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
CompletableFuture.runAsync(() -> {
Call<ParentAuthResponse> call = kidShiftApiService.parentRegister(new ParentAuthRequest(email, password));
try {
Response<ParentAuthResponse> response = call.execute();
if (response.isSuccessful()) {
ParentAuthResponse parentAuthResponse = response.body();
if (parentAuthResponse == null || parentAuthResponse.getAccessToken() == null) {
// エラー処理
logger.error("ParentAuthResponseがnullまたはAccessTokenがnullです");
return;
}
userSettings.getAppCommonSetting().setLoggedIn(true);
userSettings.getAppCommonSetting().setAccessToken(parentAuthResponse.getAccessToken());
} else {
logger.error("リクエストに失敗しました");
// エラー処理
}
} catch (Exception e) {
logger.error("リクエストに失敗しました: " + e.getMessage());
e.printStackTrace();
}
}).thenRun(() -> {
startActivity(new Intent(this, MainActivity.class));
});
});
findViewById(R.id.toLoginButton).setOnClickListener(v -> {
startActivity(new Intent(this, LoginActivity.class));
});
findViewById(R.id.toChildLoginButton).setOnClickListener(v -> {
startActivity(new Intent(this, ChildLoginActivity.class));
});
}
}

View File

@ -1,15 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<group android:scaleX="0.435"
android:scaleY="0.435"
android:translateX="6.78"
android:translateY="6.78">
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="@android:color/white"
android:pathData="M22,8c0,-0.55 -0.45,-1 -1,-1h-7c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h7C21.55,9 22,8.55 22,8zM13,16c0,0.55 0.45,1 1,1h7c0.55,0 1,-0.45 1,-1c0,-0.55 -0.45,-1 -1,-1h-7C13.45,15 13,15.45 13,16zM10.47,4.63c0.39,0.39 0.39,1.02 0,1.41l-4.23,4.25c-0.39,0.39 -1.02,0.39 -1.42,0L2.7,8.16c-0.39,-0.39 -0.39,-1.02 0,-1.41c0.39,-0.39 1.02,-0.39 1.41,0l1.42,1.42l3.54,-3.54C9.45,4.25 10.09,4.25 10.47,4.63zM10.48,12.64c0.39,0.39 0.39,1.02 0,1.41l-4.23,4.25c-0.39,0.39 -1.02,0.39 -1.42,0L2.7,16.16c-0.39,-0.39 -0.39,-1.02 0,-1.41s1.02,-0.39 1.41,0l1.42,1.42l3.54,-3.54C9.45,12.25 10.09,12.25 10.48,12.64L10.48,12.64z"/>
</group>
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,160 +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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChildLoginActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="KidShiftにログイン"
android:textSize="32dp"
app:layout_constraintBottom_toTopOf="@+id/inputContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/inputContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:weightSum="10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView">
<EditText
android:id="@+id/loginCode_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_weight="0"
android:gravity="center"
android:text="-"
android:textSize="34sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_7"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
<EditText
android:id="@+id/loginCode_8"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:maxWidth="48dp"
android:maxLength="1"
android:textSize="24sp"
tools:ignore="Autofill,LabelFor" />
</LinearLayout>
<Button
android:id="@+id/childLoginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:layout_weight="1"
android:text="ログイン"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inputContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,116 +7,47 @@
android:layout_height="match_parent"
tools:context=".LoginActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="128dp"
android:text="KidShiftにログイン"
android:textSize="32dp"
app:layout_constraintBottom_toTopOf="@+id/inputContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/inputContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="128dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:weightSum="10"
app:layout_constraintBottom_toTopOf="@+id/parentLoginButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="メールアドレス">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/parentLoginEmailEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="パスワード"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/parentLoginPasswordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Button
android:id="@+id/parentLoginButton"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:layout_weight="1"
android:paddingHorizontal="48dp"
android:text="ログイン"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inputContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/toRegisterButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
<EditText
android:id="@+id/emailEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:padding="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:text="新規登録" />
android:ems="10"
android:inputType="textEmailAddress" />
<Button
android:id="@+id/toChildLoginButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
<EditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:padding="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:text="子供ログイン" />
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

@ -7,28 +7,6 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/app_name"
android:elevation="8dp" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
@ -38,7 +16,7 @@
app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_nav" />
<com.google.android.material.bottomnavigation.BottomNavigationView
@ -49,28 +27,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/main_nav_menu" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/mainFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:clickable="true"
app:layout_constraintBottom_toTopOf="@id/bottom_nav"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/add_24px" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,122 +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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RegisterActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="128dp"
android:text="KidShiftに新規登録"
android:textSize="32dp"
app:layout_constraintBottom_toTopOf="@+id/inputContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/inputContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="128dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:weightSum="10"
app:layout_constraintBottom_toTopOf="@+id/parentRegisterButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="メールアドレス">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/parentRegisterEmailEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="パスワード"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/parentRegisterPasswordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Button
android:id="@+id/parentRegisterButton"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:layout_weight="1"
android:paddingHorizontal="48dp"
android:text="新規登録"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inputContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/toLoginButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:padding="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:text="ログイン" />
<Button
android:id="@+id/toChildLoginButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:padding="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:text="子供ログイン" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorSecondary"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="KidShift"
android:textColor="@android:color/white"
android:textSize="34sp" />
</LinearLayout>

View File

@ -1,52 +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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="32dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/userNameTextView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="UNAME_PHOLDER"
android:textSize="24sp" />
<ImageButton
android:id="@+id/userNameEditButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:background="@android:color/transparent"
android:padding="24px"
app:srcCompat="@drawable/edit_24px" />
</LinearLayout>
<TextView
android:id="@+id/emailTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="huga@example.com" />
<com.google.android.material.chip.Chip
android:id="@+id/chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保護者" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,31 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO: アイコン再検討 -->
<!-- 保護者モード / 保護者目線 -->
<item
android:id="@+id/feature_common_parent_parent_navigation"
android:icon="@drawable/home_24px"
android:title="ホーム" />
android:id="@+id/feature_parent_navigation"
android:icon="@drawable/pending_24px"
android:title="Parent" />
<!-- 保護者モード / 子供目線 -->
<item
android:id="@+id/feature_common_parent_child_navigation"
android:id="@+id/feature_child_navigation"
android:icon="@drawable/child_care_24px"
android:title="こども" />
<!-- 子供モード / 子供目線 -->
<item
android:id="@+id/feature_common_child_child_navigation"
android:icon="@drawable/home_24px"
android:title="ホーム" />
android:title="Child" />
<item
android:id="@+id/feature_wallet_parent_navigation"
android:icon="@drawable/wallet_24px"
android:title="ウォレット" />
android:id="@+id/feature_debug_navigation"
android:icon="@drawable/developer_mode_24px"
android:title="Debug" />
<item
android:id="@+id/feature_wallet_child_navigation"
android:icon="@drawable/wallet_24px"
android:title="ウォレット" />
android:id="@+id/feature_setting_navigation"
android:icon="@drawable/settings_24px"
android:title="Setting" />
</menu>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/show_account_dialog"
android:icon="@drawable/account_box_24px"
android:title="ユーザー情報" />
<item
android:id="@+id/manage_child_account"
android:icon="@drawable/manage_accounts_24px"
android:title="子供アカウントの管理" />
<!-- Divider -->
<item
android:id="@+id/divider"
android:title=""
android:enabled="false" />
<item
android:id="@+id/show_debug_dialog"
android:icon="@drawable/developer_mode_24px"
android:title="デバッグ情報" />
</menu>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 B

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -2,13 +2,10 @@
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_nav"
app:startDestination="@id/feature_common_parent_parent_navigation">
app:startDestination="@id/feature_debug_navigation">
<include app:graph="@navigation/feature_debug_navigation" />
<include app:graph="@navigation/feature_child_navigation" />
<include app:graph="@navigation/feature_common_parent_parent_navigation" />
<include app:graph="@navigation/feature_common_parent_child_navigation" />
<include app:graph="@navigation/feature_common_child_child_navigation" />
<include app:graph="@navigation/feature_wallet_parent_navigation" />
<include app:graph="@navigation/feature_wallet_child_navigation" />
<include app:graph="@navigation/feature_parent_navigation" />
<include app:graph="@navigation/feature_setting_navigation" />
</navigation>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#F1DEDE</color>
</resources>

View File

@ -1,6 +1,3 @@
<resources>
<string name="app_name">KidShift</string>
<string name="drawer_open">ドロワーを開く</string>
<string name="drawer_close">ドロワーを閉じる</string>
</resources>

View File

@ -1,10 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7" // TODO:
}
}
plugins {
alias(libs.plugins.androidApplication) apply false
id 'com.google.dagger.hilt.android' version '2.44' apply false

View File

@ -1,16 +0,0 @@
# :data モジュール
## 概要
UI側に対してバックエンドとの通信, キャッシュ処理を隠蔽するためのラッパーモジュール
## 内容
### KSActions
#### 概要
モジュール内で共通処理を切り出したモジュール
### ChildData
#### 概要
子データを取得するためのモジュール
#### 一覧
WIP
#### メモ

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.callback.ChildModelCallback;
public interface ChildData {
@ -19,31 +18,25 @@ public interface ChildData {
* 子ユーザー一覧取得
* @return List<ChildModel> 子ユーザー一覧
*/
CompletableFuture<List<ChildModel>> getChildList(ChildModelCallback callback);
/**
* 子ユーザー一覧をサーバーから直接取得(キャッシュ無視)
* @return List<ChildModel> 子ユーザー一覧
*/
CompletableFuture<List<ChildModel>> getChildListDirect();
CompletableFuture<List<ChildModel>> getChildList();
/**
* 子ユーザー情報更新
* @param child 子ユーザー情報
*/
CompletableFuture<ChildModel> updateChild(ChildModel child);
void updateChild(ChildModel child);
/**
* 子ユーザー追加
* @param child 子ユーザー情報
*/
CompletableFuture<ChildModel> addChild(ChildModel child);
void addChild(ChildModel child);
/**
* 子ユーザー削除
* @param childId 子ID
*/
CompletableFuture<Void> removeChild(String childId);
void removeChild(String childId);
/**
* 子ユーザーログインコード発行

View File

@ -5,12 +5,11 @@ import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.tasks.TaskItemModel;
/**
* データの同期などを提供する内部ユーティリティ
* データの同期など, ユーザーからの操作に基づかない処理を行う
*/
public interface KSActions {
@ -26,9 +25,4 @@ public interface KSActions {
*/
CompletableFuture<ParentModel> syncParent();
/**
* 履歴情報同期
*/
CompletableFuture<List<HistoryModel>> syncHistory(String childId);
}

View File

@ -14,22 +14,10 @@ public interface ParentData {
*/
CompletableFuture<ParentModel> getParent(ParentModelCallback callback);
/**
* 親ユーザー情報取得
* @return 親ユーザー情報
*/
CompletableFuture<ParentModel> getParentDirect();
/**
* 親ユーザー情報取得
* @return 親ユーザー情報
*/
CompletableFuture<ParentModel> getParentCache();
/**
* 親ユーザー情報更新
* @param parent 親ユーザー情報
*/
CompletableFuture<Void> updateParent(ParentModel parent);
void updateParent(ParentModel parent);
}

View File

@ -1,22 +1,11 @@
package one.nem.kidshift.data;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import one.nem.kidshift.model.HistoryModel;
public interface RewardData {
/**
* 現時点の合計報酬額を取得する
* @return Integer 合計報酬額
*/
CompletableFuture<Integer> getTotalReward(String childId);
CompletableFuture<List<HistoryModel>> getRewardHistoryList();
CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId);
CompletableFuture<Void> payReward(String historyId);
CompletableFuture<Void> payReward(List<String> historyIds);
CompletableFuture<Integer> getTotalReward();
}

View File

@ -27,19 +27,19 @@ public interface TaskData {
* タスクを追加する
* @param task タスク
*/
CompletableFuture<TaskItemModel> addTask(TaskItemModel task);
void addTask(TaskItemModel task);
/**
* タスクを削除する
* @param taskId タスクID
*/
CompletableFuture<Void> removeTask(String taskId);
void removeTask(String taskId);
/**
* タスクを更新する
* @param task タスク
*/
CompletableFuture<Void> updateTask(TaskItemModel task);
void updateTask(TaskItemModel task);
// 子側
@ -55,5 +55,5 @@ public interface TaskData {
* @param taskId タスクID
* @param childId 子ID
*/
CompletableFuture<Void> recordTaskCompletion(String taskId, String childId);
void recordTaskCompletion(String taskId, String childId);
}

View File

@ -37,9 +37,6 @@ public interface UserSettings {
boolean isChildMode();
void setChildMode(boolean childMode);
String getChildId();
void setChildId(String childId);
}
interface SharedPrefCache {

View File

@ -2,182 +2,73 @@ package one.nem.kidshift.data.impl;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.inject.Inject;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.child.ChildLoginCodeResponse;
import one.nem.kidshift.data.retrofit.model.child.ChildResponse;
import one.nem.kidshift.data.retrofit.model.child.ChildListResponse;
import one.nem.kidshift.data.retrofit.model.converter.ChildModelConverter;
import one.nem.kidshift.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.callback.ChildModelCallback;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
import retrofit2.Response;
public class ChildDataImpl implements ChildData {
private final KidShiftApiService kidShiftApiService;
private final KSActions ksActions;
private final CacheWrapper cacheWrapper;
private final KSLogger logger;
private KidShiftApiService kidShiftApiService;
private KSLogger logger;
@Inject
public ChildDataImpl(KidShiftApiService kidShiftApiService, KSActions ksActions, CacheWrapper cacheWrapper, KSLoggerFactory loggerFactory) {
public ChildDataImpl(KidShiftApiService kidShiftApiService, KSLoggerFactory loggerFactory) {
this.kidShiftApiService = kidShiftApiService;
this.ksActions = ksActions;
this.cacheWrapper = cacheWrapper;
this.logger = loggerFactory.create("ChildDataImpl");
}
@Override
public CompletableFuture<ChildModel> getChild(String childId) {
return CompletableFuture.supplyAsync(() -> {
Call<ChildResponse> call = kidShiftApiService.getChild(childId);
try {
Response<ChildResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
logger.info("子供取得成功(childId: " + response.body().getId() + ")");
return ChildModelConverter.childResponseToChildModel(response.body());
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@Override
public CompletableFuture<List<ChildModel>> getChildList(ChildModelCallback callback) { // TODO: リファクタリング
return CompletableFuture.supplyAsync(() -> {
logger.debug("子供リスト取得開始");
AtomicReference<List<ChildModel>> childListTmp = new AtomicReference<>();
Thread thread = new Thread(() -> ksActions.syncChildList().thenAccept(childList -> {
if (childListTmp.get() == null || childListTmp.get().isEmpty()) {
logger.debug("子供リスト取得完了: キャッシュよりはやく取得完了 or キャッシュ無し");
if (childList == null || childList.isEmpty()) {
callback.onUnchanged();
} else {
callback.onUpdated(childList);
}
} else {
boolean isChanged =
childList.size() != childListTmp.get().size() ||
childList.stream().anyMatch(child -> childListTmp.get().stream().noneMatch(childTmp -> child.getId().equals(childTmp.getId())));
if (isChanged) {
logger.debug("子供リスト取得完了: キャッシュと比較して変更あり");
callback.onUpdated(childList);
} else {
logger.debug("子供リスト取得完了: キャッシュと比較して変更なし");
callback.onUnchanged();
}
}
}).exceptionally(e -> {
logger.error("子供リスト取得失敗: " + e.getMessage());
callback.onFailed(e.getMessage());
return null;
}));
thread.start();
return cacheWrapper.getChildList().thenApply(childList -> {
if (childList == null || childList.isEmpty()) {
try {
logger.debug("キャッシュ無: 子供リスト取得スレッド待機");
thread.join();
return cacheWrapper.getChildList().join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
logger.debug("キャッシュ有 (子供数: " + childList.size() + ")");
childListTmp.set(childList);
return childList;
}
}).join();
});
}
@Override
public CompletableFuture<List<ChildModel>> getChildListDirect() {
return ksActions.syncChildList();
}
@Override
public CompletableFuture<ChildModel> updateChild(ChildModel child) {
public CompletableFuture<List<ChildModel>> getChildList() { // TODO-rca: DBにキャッシュするように修正する
return CompletableFuture.supplyAsync(() -> {
Call<ChildResponse> call = kidShiftApiService.updateChild(ChildModelConverter.childModelToChildAddRequest(child), child.getId());
Call<ChildListResponse> call = kidShiftApiService.getChildList();
try {
Response<ChildResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
logger.info("子供更新成功(childId: " + response.body().getId() + ")");
return ChildModelConverter.childResponseToChildModel(response.body());
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
Response<ChildListResponse> response = call.execute();
if (!response.isSuccessful()) return null; // TODO-rca: nullとするかは検討
ChildListResponse body = response.body();
if (body == null) return null;
return ChildModelConverter.childListResponseToChildModelList(body);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@Override
public CompletableFuture<ChildModel> addChild(ChildModel child) {
return CompletableFuture.supplyAsync(() -> {
Call<ChildResponse> call = kidShiftApiService.addChild(ChildModelConverter.childModelToChildAddRequest(child));
try {
Response<ChildResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
logger.info("子供追加成功(childId: " + response.body().getId() + ")");
return ChildModelConverter.childResponseToChildModel(response.body());
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@Override
public CompletableFuture<Void> removeChild(String childId) {
return CompletableFuture.supplyAsync(() -> {
Call<Void> call = kidShiftApiService.removeChild(childId);
try {
Response<Void> response = call.execute();
if (response.isSuccessful()) {
logger.info("子供削除成功(childId: " + childId + ")");
logger.error(e.getMessage());
return null;
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@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 CompletableFuture.supplyAsync(() -> {
Call<ChildLoginCodeResponse> call = kidShiftApiService.issueLoginCode(childId);
try {
Response<ChildLoginCodeResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
return response.body().getCode();
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return null;
}
}

View File

@ -10,15 +10,12 @@ import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.child.ChildListResponse;
import one.nem.kidshift.data.retrofit.model.converter.ChildModelConverter;
import one.nem.kidshift.data.retrofit.model.converter.HistoryModelConverter;
import one.nem.kidshift.data.retrofit.model.converter.ParentModelConverter;
import one.nem.kidshift.data.retrofit.model.converter.TaskModelConverter;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.data.retrofit.model.task.HistoryListResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.utils.KSLogger;
@ -64,7 +61,7 @@ public class KSActionsImpl implements KSActions {
return fetchChildListAsync().thenCombine(fetchTaskListAsync(), (childListResponse, taskListResponse) -> {
Thread cacheThread = new Thread(() -> {
logger.debug("キャッシュ更新スレッド開始(スレッドID: " + Thread.currentThread().getId() + ")");
cacheWrapper.updateChildTaskCache(ChildModelConverter.childListResponseToChildModelList(childListResponse),
cacheWrapper.updateCache(ChildModelConverter.childListResponseToChildModelList(childListResponse),
TaskModelConverter.taskListResponseToTaskItemModelList(taskListResponse)).join();
logger.info("キャッシュ更新完了");
});
@ -155,31 +152,4 @@ public class KSActionsImpl implements KSActions {
}
});
}
@Override
public CompletableFuture<List<HistoryModel>> syncHistory(String childId) {
CompletableFuture<HistoryListResponse> callHistoryApi = CompletableFuture.supplyAsync(() -> {
Call<HistoryListResponse> call = kidShiftApiService.getHistory(childId, true); // TODO: containPaidを引数に
try {
Response<HistoryListResponse> response = call.execute();
if (!response.isSuccessful()) {
logger.error("Error fetching history list: " + response.errorBody().string());
throw new RuntimeException("Error fetching history list: " + response.errorBody().string());
}
return response.body();
} catch (Exception e) {
logger.error("Error fetching history list");
throw new RuntimeException(e);
}
});
CompletableFuture<TaskListResponse> callTaskApi = fetchTaskListAsync();
return CompletableFuture.allOf(callHistoryApi, callTaskApi).thenApplyAsync(result -> {
HistoryListResponse historyListResponse = callHistoryApi.join();
TaskListResponse taskListResponse = callTaskApi.join();
return HistoryModelConverter.historyListResponseAndTaskListResponseToHistoryModelList(historyListResponse, taskListResponse);
});
// TODO: キャッシュ更新
}
}

View File

@ -8,26 +8,21 @@ 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.data.retrofit.model.parent.ParentRenameRequest;
import one.nem.kidshift.model.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
public class ParentDataImpl implements ParentData {
private final UserSettings userSettings;
private final KidShiftApiService kidShiftApiService;
private final KSLogger logger;
private final KSActions ksActions;
@Inject
public ParentDataImpl(KidShiftApiService kidShiftApiService, UserSettings userSettings, KSLoggerFactory ksLoggerFactory, KSActions ksActions) {
this.kidShiftApiService = kidShiftApiService;
public ParentDataImpl(KidShiftApiService kidshiftApiService, UserSettings userSettings, KSLoggerFactory ksLoggerFactory, KSActions ksActions) {
this.userSettings = userSettings;
this.logger = ksLoggerFactory.create("ParentDataImpl");
this.ksActions = ksActions;
@ -51,27 +46,8 @@ public class ParentDataImpl implements ParentData {
}
@Override
public CompletableFuture<ParentModel> getParentDirect() {
return ksActions.syncParent();
}
public void updateParent(ParentModel parent) {
@Override
public CompletableFuture<ParentModel> getParentCache() {
return CompletableFuture.supplyAsync(() -> userSettings.getCache().getParent());
}
@Override
public CompletableFuture<Void> updateParent(ParentModel parent) {
Call<ParentInfoResponse> call = kidShiftApiService.renameParent(new ParentRenameRequest(parent.getName()));
try {
ParentInfoResponse response = call.execute().body();
if (response == null) {
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
return CompletableFuture.completedFuture(null);
}
}
}

View File

@ -0,0 +1,27 @@
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;
import one.nem.kidshift.utils.KSLogger;
public class RewardDataDummyImpl implements RewardData {
private final Faker faker;
@Inject
public RewardDataDummyImpl() {
faker = new Faker();
// logger.setTag("RewardDataDummyImpl");
}
@Override
public CompletableFuture<Integer> getTotalReward() {
return CompletableFuture.supplyAsync(() -> faker.number().numberBetween(0, 1000));
}
}

View File

@ -1,85 +0,0 @@
package one.nem.kidshift.data.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
public class RewardDataImpl implements RewardData {
private final UserSettings userSettings;
private final KSActions ksActions;
private final CacheWrapper cacheWrapper;
private final KSLogger logger;
private final ChildData childData;
private final KidShiftApiService kidShiftApiService;
@Inject
public RewardDataImpl(KSLoggerFactory ksLoggerFactory, CacheWrapper cacheWrapper, UserSettings userSettings, KSActions ksActions, ChildData childData, KidShiftApiService kidShiftApiService) {
this.userSettings = userSettings;
this.ksActions = ksActions;
this.cacheWrapper = cacheWrapper;
this.childData = childData;
this.kidShiftApiService = kidShiftApiService;
this.logger = ksLoggerFactory.create("RewardDataImpl");
}
@Override
public CompletableFuture<Integer> getTotalReward(String childId) { // TODO: localCacheを使う
return CompletableFuture.supplyAsync(() -> ksActions.syncHistory(childId).join().stream().mapToInt(HistoryModel::getReward).sum());
}
@Override
public CompletableFuture<List<HistoryModel>> getRewardHistoryList() { // TODO: localCacheを使う
List<HistoryModel> historyModels = new ArrayList<>();
return childData.getChildListDirect().thenAccept(childModels -> {
childModels.forEach(childModel -> {
historyModels.addAll(ksActions.syncHistory(childModel.getId()).join());
});
}).thenApply(v -> historyModels);
}
@Override
public CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId) { // TODO: localCacheを使う
return CompletableFuture.supplyAsync(() -> ksActions.syncHistory(childId).join());
}
@Override
public CompletableFuture<Void> payReward(String historyId) {
return CompletableFuture.runAsync(() -> {
Call<Void> call = kidShiftApiService.payHistory(historyId, true);
try {
call.execute();
} catch (Exception e) {
logger.error("Failed to pay reward : " + e.getMessage());
}
});
}
@Override
public CompletableFuture<Void> payReward(List<String> historyIds) {
return CompletableFuture.runAsync(() -> {
historyIds.forEach(historyId -> { // TODO: API側でリストに対応する
Call<Void> call = kidShiftApiService.payHistory(historyId, true);
try {
call.execute();
} catch (Exception e) {
logger.error("Failed to pay reward : " + e.getMessage());
}
});
});
}
}

View File

@ -8,28 +8,21 @@ import javax.inject.Inject;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.TaskData;
import one.nem.kidshift.data.retrofit.KidShiftApiService;
import one.nem.kidshift.data.retrofit.model.converter.TaskModelConverter;
import one.nem.kidshift.data.retrofit.model.task.TaskResponse;
import one.nem.kidshift.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.callback.TaskItemModelCallback;
import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
import retrofit2.Response;
public class TaskDataImpl implements TaskData {
private final KSActions ksActions;
private final KidShiftApiService kidShiftApiService;
private final CacheWrapper cacheWrapper;
private final KSLogger logger;
@Inject
public TaskDataImpl(KSActions ksActions, KidShiftApiService kidShiftApiService, CacheWrapper cacheWrapper, KSLoggerFactory loggerFactory) {
public TaskDataImpl(KSActions ksActions, CacheWrapper cacheWrapper, KSLoggerFactory loggerFactory) {
this.ksActions = ksActions;
this.kidShiftApiService = kidShiftApiService;
this.cacheWrapper = cacheWrapper;
this.logger = loggerFactory.create("TaskDataImpl");
}
@ -39,7 +32,8 @@ public class TaskDataImpl implements TaskData {
return CompletableFuture.supplyAsync(() -> {
logger.debug("タスク取得開始");
AtomicReference<List<TaskItemModel>> taskListTmp = new AtomicReference<>();
Thread thread = new Thread(() -> ksActions.syncTasks().thenAccept(taskList -> {
Thread thread = new Thread(() -> {
ksActions.syncTasks().thenAccept(taskList -> {
if (taskListTmp.get() == null || taskListTmp.get().isEmpty()) {
logger.debug("タスク取得完了: キャッシュよりはやく取得完了 or キャッシュ無し");
if (taskList == null || taskList.isEmpty()) {
@ -64,7 +58,8 @@ public class TaskDataImpl implements TaskData {
logger.error("タスク取得失敗: " + e.getMessage());
callback.onFailed(e.getMessage());
return null;
}));
});
});
thread.start();
return cacheWrapper.getTaskList().thenApply(taskList -> {
if (taskList == null || taskList.isEmpty()) {
@ -90,62 +85,18 @@ public class TaskDataImpl implements TaskData {
}
@Override
public CompletableFuture<TaskItemModel> addTask(TaskItemModel task) {
return CompletableFuture.supplyAsync(() -> {
Call<TaskResponse> call = kidShiftApiService.addTask(TaskModelConverter.taskItemModelToTaskAddRequest(task));
try {
Response<TaskResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
logger.info("タスク追加成功(taskId: " + response.body().getId() + ")");
return TaskModelConverter.taskResponseToTaskItemModel(response.body());
} else {
logger.error("タスク追加失敗: HTTP Status: " + response.code());
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
public void addTask(TaskItemModel task) {
}
@Override
public CompletableFuture<Void> removeTask(String taskId) {
return CompletableFuture.supplyAsync(() -> {
Call<Void> call = kidShiftApiService.removeTask(taskId);
try {
Response<Void> response = call.execute();
if (response.isSuccessful()) {
logger.info("タスク削除成功(taskId: " + taskId + ")");
return null;
} else {
logger.error("タスク削除失敗: HTTP Status: " + response.code());
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
public void removeTask(String taskId) {
}
@Override
public CompletableFuture<Void> updateTask(TaskItemModel task) {
return CompletableFuture.supplyAsync(() -> {
Call<TaskResponse> call = kidShiftApiService.updateTask(TaskModelConverter.taskItemModelToTaskAddRequest(task), task.getId());
try {
Response<TaskResponse> response = call.execute();
if (response.isSuccessful()) {
logger.info("タスク更新成功(taskId: " + task.getId() + ")");
// return response.body();
return null;
} else {
logger.error("タスク更新失敗: HTTP Status: " + response.code());
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
public void updateTask(TaskItemModel task) {
}
@Override
@ -154,21 +105,7 @@ public class TaskDataImpl implements TaskData {
}
@Override
public CompletableFuture<Void> recordTaskCompletion(String taskId, String childId) {
return CompletableFuture.supplyAsync(() -> {
Call<Void> call = kidShiftApiService.completeTask(taskId, childId);
try {
Response<Void> response = call.execute();
if (response.isSuccessful()) {
logger.info("タスク完了処理成功(taskId: " + taskId + ", childId: " + childId + ")");
return null;
} else {
logger.error("タスク完了処理失敗: HTTP Status: " + response.code());
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
public void recordTaskCompletion(String taskId, String childId) {
}
}

View File

@ -77,7 +77,6 @@ public class UserSettingsImpl implements UserSettings {
boolean loggedIn;
String accessToken;
boolean childMode;
String childId;
AppCommonSettingImpl() {
sharedPrefUtils = sharedPrefUtilsFactory.create("user_settings");
@ -86,12 +85,10 @@ public class UserSettingsImpl implements UserSettings {
loggedIn = appCommonSetting.isLoggedIn();
accessToken = appCommonSetting.getAccessToken().isEmpty() ? "" : appCommonSetting.getAccessToken();
childMode = appCommonSetting.isChildMode();
childId = appCommonSetting.getChildId().isEmpty() ? "" : appCommonSetting.getChildId();
} else {
loggedIn = false;
accessToken = "";
childMode = false;
childId = "";
}
}
@ -131,17 +128,6 @@ public class UserSettingsImpl implements UserSettings {
this.childMode = childMode;
save();
}
@Override
public String getChildId() {
return childId;
}
@Override
public void setChildId(String childId) {
this.childId = childId;
save();
}
}
public class ApiSettingImpl implements UserSettings.ApiSetting {

View File

@ -4,12 +4,11 @@ import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.data.impl.ParentDataImpl;
@Module
@InstallIn(SingletonComponent.class)
@InstallIn(FragmentComponent.class)
public abstract class ParentDataModule {
@Binds

View File

@ -5,12 +5,12 @@ import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.impl.RewardDataImpl;
import one.nem.kidshift.data.impl.RewardDataDummyImpl;
@Module
@InstallIn(FragmentComponent.class)
public abstract class RewardDataModule {
abstract public class RewardDataDummyModule {
@Binds
public abstract RewardData bindRewardData(RewardDataImpl rewardDataImpl);
public abstract RewardData bindRewardData(RewardDataDummyImpl rewardDataDummyImpl);
}

View File

@ -3,15 +3,10 @@ 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.ChildLoginCodeResponse;
import one.nem.kidshift.data.retrofit.model.child.ChildResponse;
import one.nem.kidshift.data.retrofit.model.child.auth.ChildAuthRequest;
import one.nem.kidshift.data.retrofit.model.child.auth.ChildAuthResponse;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.data.retrofit.model.parent.ParentRenameRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthResponse;
import one.nem.kidshift.data.retrofit.model.task.HistoryListResponse;
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;
@ -19,11 +14,11 @@ 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;
import retrofit2.http.Query;
public interface KidShiftApiService {
@ -36,32 +31,7 @@ public interface KidShiftApiService {
* @return ParentLoginResponse
*/
@POST("/parent/auth/login")
Call<ParentAuthResponse> parentLogin(@Body ParentAuthRequest request);
/**
* 保護者登録処理
* @param request ParentRegisterRequest
* @return ParentRegisterResponse
*/
@POST("/parent/auth/register")
Call<ParentAuthResponse> parentRegister(@Body ParentAuthRequest request);
/**
* 保護者情報更新処理
* @param request ParentRenameRequest
* @return ParentInfoResponse
*/
@PUT("/parent/account")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ParentInfoResponse> renameParent(@Body ParentRenameRequest request);
/**
* 子供ログイン処理
* @param request ChildAuthRequest
* @return ChildAuthResponse
*/
@POST("/child/auth/login")
Call<ChildAuthResponse> childLogin(@Body ChildAuthRequest request);
Call<ParentLoginResponse> parentLogin(@Body ParentLoginRequest request);
/**
* 保護者アカウント情報取得処理
@ -77,7 +47,7 @@ public interface KidShiftApiService {
* タスク一覧取得
* @return TaskListResponse
*/
@GET("/task")
@GET("/parent/task")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskListResponse> getTasks();
@ -86,7 +56,7 @@ public interface KidShiftApiService {
* @param request TaskAddRequest
* @return TaskResponse
*/
@POST("/task")
@POST("/parent/task")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> addTask(@Body TaskAddRequest request);
@ -96,7 +66,7 @@ public interface KidShiftApiService {
* @param id タスクID
* @return TaskResponse
*/
@PUT("/task/{id}")
@PUT("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> updateTask(@Body TaskAddRequest request, @Path("id") String id);
@ -105,7 +75,7 @@ public interface KidShiftApiService {
* @param id タスクID
* @return Void
*/
@DELETE("/task/{id}")
@DELETE("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> removeTask(@Path("id") String id); // TODO-rca: OK responseをパース
@ -114,7 +84,7 @@ public interface KidShiftApiService {
* @param id タスクID
* @return TaskResponse
*/
@GET("/task/{id}")
@GET("/parent/task/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<TaskResponse> getTask(@Path("id") String id);
@ -123,9 +93,9 @@ public interface KidShiftApiService {
* @param id タスクID
* @return Void
*/
@POST("/task/{id}/complete")
@POST("/parent/task/{id}/complete")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> completeTask(@Path("id") String id, @Query("childId") String childId);
Call<Void> completeTask(@Path("id") String id); // TODO-rca: OK responseをパース
// Child APIs
@ -133,61 +103,17 @@ public interface KidShiftApiService {
* 子供一覧取得
* @return ChildListResponse
*/
@GET("/child")
@GET("/parent/child")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildListResponse> getChildList();
/**
* 子供情報取得
* @param id 子供ID
* @return ChildResponse
*/
@GET("/child/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildResponse> getChild(@Path("id") String id);
/**
* 子供追加
* @param request ChildAddRequest
* @return ChildResponse
*/
@POST("/child")
@POST("/parent/child")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildResponse> addChild(@Body ChildAddRequest request);
/**
* 子供更新
* @param request ChildAddRequest
* @param id 子供ID
* @return ChildResponse
*/
@PUT("/child/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildResponse> updateChild(@Body ChildAddRequest request, @Path("id") String id);
/**
* 子供削除
* @param id 子供ID
* @return Void
*/
@DELETE("/child/{id}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> removeChild(@Path("id") String id);
/**
* 子供ログインコード発行
* @param id 子供ID
* @return ChildLoginCodeResponse
*/
@GET("/child/{id}/login")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildLoginCodeResponse> issueLoginCode(@Path("id") String id);
@GET("/task/history/{childId}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<HistoryListResponse> getHistory(@Path("childId") String childId, @Query("containPaid") boolean containPaid);
@POST("/task/history/{historyId}/paid")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> payHistory(@Path("historyId") String historyId, @Query("isPaid") boolean isPaid);
}

View File

@ -1,20 +0,0 @@
package one.nem.kidshift.data.retrofit.model.child;
public class ChildLoginCodeResponse {
private int code;
public ChildLoginCodeResponse(int code) {
this.code = code;
}
public ChildLoginCodeResponse() {
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}

View File

@ -1,17 +0,0 @@
package one.nem.kidshift.data.retrofit.model.child.auth;
public class ChildAuthRequest {
private String loginCode;
public ChildAuthRequest(String loginCode) {
this.loginCode = loginCode;
}
public String getLoginCode() {
return loginCode;
}
public void setLoginCode(String loginCode) {
this.loginCode = loginCode;
}
}

View File

@ -1,30 +0,0 @@
package one.nem.kidshift.data.retrofit.model.child.auth;
public class ChildAuthResponse {
private String accessToken;
private String childId;
public ChildAuthResponse() {
}
public ChildAuthResponse(String accessToken, String childId) {
this.accessToken = accessToken;
this.childId = childId;
}
public String getChildId() {
return childId;
}
public void setChildId(String childId) {
this.childId = childId;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}

View File

@ -1,79 +0,0 @@
package one.nem.kidshift.data.retrofit.model.converter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import one.nem.kidshift.data.retrofit.model.task.HistoryListResponse;
import one.nem.kidshift.data.retrofit.model.task.HistoryResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskResponse;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.model.tasks.TaskItemModel;
public class HistoryModelConverter { // TODO: JavaDoc
public static HistoryModel historyResponseToHistoryModel(HistoryResponse historyResponse) {
HistoryModel historyModel = new HistoryModel();
historyModel.setId(historyResponse.getId());
historyModel.setTaskId(historyResponse.getTaskId());
historyModel.setChildId(historyResponse.getChildId());
historyModel.setRegisteredAt(historyResponse.getRegisteredAt());
historyModel.setPaid(historyResponse.isPaid());
return historyModel;
}
public static HistoryResponse historyModelToHistoryResponse(HistoryModel historyModel) {
HistoryResponse historyResponse = new HistoryResponse();
historyResponse.setId(historyModel.getId());
historyResponse.setTaskId(historyModel.getTaskId());
historyResponse.setChildId(historyModel.getChildId());
historyResponse.setRegisteredAt(historyModel.getRegisteredAt());
historyResponse.setPaid(historyModel.isPaid());
return historyResponse;
}
public static List<HistoryModel> historyListResponseToHistoryModelList(HistoryListResponse historyListResponse) {
List<HistoryModel> historyModelList = new ArrayList<>();
for (HistoryResponse historyResponse : historyListResponse.getList()) {
historyModelList.add(historyResponseToHistoryModel(historyResponse));
}
return historyModelList;
}
public static HistoryListResponse historyModelListToHistoryListResponse(List<HistoryModel> historyModelList) {
HistoryListResponse historyListResponse = new HistoryListResponse();
List<HistoryResponse> historyResponseList = new ArrayList<>();
for (HistoryModel historyModel : historyModelList) {
historyResponseList.add(historyModelToHistoryResponse(historyModel));
}
historyListResponse.setList(historyResponseList);
return historyListResponse;
}
private static TaskResponse emptyTaskItemModel() {
TaskResponse taskResponse = new TaskResponse();
taskResponse.setId("");
taskResponse.setName("Critical Error occurred(Your data (on server) is may be corrupted.)");
taskResponse.setReward(0);
return taskResponse;
}
public static List<HistoryModel> historyListResponseAndTaskListResponseToHistoryModelList(HistoryListResponse historyListResponse, TaskListResponse taskListResponse) {
List<HistoryModel> historyModelList = new ArrayList<>();
for (HistoryResponse historyResponse : historyListResponse.getList()) {
HistoryModel historyModel = historyResponseToHistoryModel(historyResponse);
if (taskListResponse == null || taskListResponse.getList() == null || taskListResponse.getList().isEmpty()) {
continue;
}
TaskItemModel taskItemModel = TaskModelConverter.taskResponseToTaskItemModel(
Objects.requireNonNull(taskListResponse.getList().stream()
.filter(taskResponse -> taskResponse.getId().equals(historyModel.getTaskId()))
.findFirst().orElse(emptyTaskItemModel())));
historyModel.setTaskName(taskItemModel.getName());
historyModel.setReward(taskItemModel.getReward());
historyModelList.add(historyModel);
}
return historyModelList;
}
}

View File

@ -11,7 +11,7 @@ public class ParentModelConverter {
* @return ParentModel
*/
public static ParentModel parentInfoResponseToParentModel(ParentInfoResponse parentInfoResponse) {
return new ParentModel(parentInfoResponse.getId(), parentInfoResponse.getDisplay_name(), parentInfoResponse.getEmail());
return new ParentModel(parentInfoResponse.getId(), parentInfoResponse.getDisplayName(), parentInfoResponse.getEmail());
}
/**

View File

@ -3,7 +3,6 @@ package one.nem.kidshift.data.retrofit.model.converter;
import java.util.List;
import java.util.stream.Collectors;
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 one.nem.kidshift.model.tasks.TaskItemModel;
@ -48,13 +47,4 @@ public class TaskModelConverter {
public static List<TaskItemModel> taskListResponseToTaskItemModelList(TaskListResponse taskListResponse) {
return taskListResponse.getList().stream().map(TaskModelConverter::taskResponseToTaskItemModel).collect(Collectors.toList());
}
public static TaskAddRequest taskItemModelToTaskAddRequest(TaskItemModel taskItemModel) {
TaskAddRequest request = new TaskAddRequest();
request.setName(taskItemModel.getName());
request.setReward(taskItemModel.getReward());
request.setBgColor(taskItemModel.getBgColor());
request.setIconEmoji(taskItemModel.getIconEmoji());
return request;
}
}

View File

@ -4,18 +4,18 @@ public class ParentInfoResponse {
private String id;
private String email;
private String display_name;
private String displayName;
/**
* コンストラクタ (全プロパティ)
* @param id 親ID
* @param email メールアドレス
* @param display_name 表示名
* @param displayName 表示名
*/
public ParentInfoResponse(String id, String email, String display_name) {
public ParentInfoResponse(String id, String email, String displayName) {
this.id = id;
this.email = email;
this.display_name = display_name;
this.displayName = displayName;
}
public String getId() {
@ -26,8 +26,8 @@ public class ParentInfoResponse {
return email;
}
public String getDisplay_name() {
return display_name;
public String getDisplayName() {
return displayName;
}
public void setId(String id) {
@ -38,7 +38,7 @@ public class ParentInfoResponse {
this.email = email;
}
public void setDisplay_name(String display_name) {
this.display_name = display_name;
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}

View File

@ -1,21 +0,0 @@
package one.nem.kidshift.data.retrofit.model.parent;
public class ParentRenameRequest {
private String displayName;
/**
* コンストラクタ (全プロパティ)
* @param displayName 表示名
*/
public ParentRenameRequest(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}

View File

@ -1,6 +1,6 @@
package one.nem.kidshift.data.retrofit.model.parent.auth;
public class ParentAuthRequest {
public class ParentLoginRequest {
private String email;
private String password;
@ -9,7 +9,7 @@ public class ParentAuthRequest {
* @param email メールアドレス
* @param password パスワード
*/
public ParentAuthRequest(String email, String password) {
public ParentLoginRequest(String email, String password) {
this.email = email;
this.password = password;
}

View File

@ -1,13 +1,13 @@
package one.nem.kidshift.data.retrofit.model.parent.auth;
public class ParentAuthResponse {
public class ParentLoginResponse {
private String accessToken;
/**
* コンストラクタ (全プロパティ)
* @param accessToken アクセストークン
*/
public ParentAuthResponse(String accessToken) {
public ParentLoginResponse(String accessToken) {
this.accessToken = accessToken;
}

View File

@ -1,62 +0,0 @@
package one.nem.kidshift.data.retrofit.model.task;
import java.util.Date;
public class HistoryBaseItem {
private String id;
private String taskId;
private String childId;
private Date registeredAt;
private boolean isPaid;
public HistoryBaseItem(String id, String taskId, String childId, Date registeredAt, boolean isPaid) {
this.id = id;
this.taskId = taskId;
this.childId = childId;
this.registeredAt = registeredAt;
this.isPaid = isPaid;
}
public HistoryBaseItem() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getChildId() {
return childId;
}
public void setChildId(String childId) {
this.childId = childId;
}
public Date getRegisteredAt() {
return registeredAt;
}
public void setRegisteredAt(Date registeredAt) {
this.registeredAt = registeredAt;
}
public boolean isPaid() {
return isPaid;
}
public void setPaid(boolean isPaid) {
this.isPaid = isPaid;
}
}

View File

@ -1,22 +0,0 @@
package one.nem.kidshift.data.retrofit.model.task;
import java.util.List;
public class HistoryListResponse {
List<HistoryResponse> list;
public HistoryListResponse(List<HistoryResponse> list) {
this.list = list;
}
public HistoryListResponse() {
}
public List<HistoryResponse> getList() {
return list;
}
public void setList(List<HistoryResponse> list) {
this.list = list;
}
}

View File

@ -1,5 +0,0 @@
package one.nem.kidshift.data.retrofit.model.task;
public class HistoryResponse extends HistoryBaseItem {
// Additional fields
}

View File

@ -4,15 +4,13 @@ import androidx.room.Database;
import androidx.room.RoomDatabase;
import one.nem.kidshift.data.room.dao.ChildCacheDao;
import one.nem.kidshift.data.room.dao.HistoryCacheDao;
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.HistoryCacheEntity;
import one.nem.kidshift.data.room.entity.TaskCacheEntity;
import one.nem.kidshift.data.room.entity.TaskChildLinkageEntity;
@Database(entities = {ChildCacheEntity.class, TaskCacheEntity.class, TaskChildLinkageEntity.class, HistoryCacheEntity.class}, version = 2)
@Database(entities = {ChildCacheEntity.class, TaskCacheEntity.class, TaskChildLinkageEntity.class}, version = 1)
public abstract class KidShiftDatabase extends RoomDatabase {
public abstract ChildCacheDao childCacheDao();
@ -21,6 +19,4 @@ public abstract class KidShiftDatabase extends RoomDatabase {
public abstract TaskChildLinkageDao taskChildLinkageDao();
public abstract HistoryCacheDao historyCacheDao();
}

View File

@ -1,16 +0,0 @@
package one.nem.kidshift.data.room.converter;
import androidx.room.TypeConverter;
public class DateTypeConverter {
@TypeConverter
public static java.util.Date fromTimestamp(Long value) {
return value == null ? null : new java.util.Date(value);
}
@TypeConverter
public static Long dateToTimestamp(java.util.Date date) {
return date == null ? null : date.getTime();
}
}

View File

@ -1,48 +0,0 @@
package one.nem.kidshift.data.room.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import java.util.List;
import one.nem.kidshift.data.room.entity.HistoryCacheEntity;
import one.nem.kidshift.data.room.model.HistoryWithTask;
@Dao
public interface HistoryCacheDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertHistory(HistoryCacheEntity history);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertHistoryList(List<HistoryCacheEntity> historyList);
@Query("SELECT * FROM history_cache WHERE id = :historyId")
HistoryCacheEntity getHistory(String historyId);
@Query("SELECT * FROM history_cache")
List<HistoryCacheEntity> getHistoryList();
@Query("SELECT * FROM history_cache WHERE child_id = :childId")
List<HistoryCacheEntity> getHistoryListByChildId(String childId);
@Transaction
@Query("SELECT * FROM history_cache")
List<HistoryWithTask> getHistoryWithTasks();
@Transaction
@Query("SELECT * FROM history_cache WHERE child_id = :childId")
List<HistoryWithTask> getHistoryWithTasksByChildId(String childId);
@Query("SELECT * FROM history_cache WHERE task_id = :taskId")
List<HistoryCacheEntity> getHistoryListByTaskId(String taskId);
@Query("DELETE FROM history_cache WHERE id = :historyId")
void deleteHistory(String historyId);
@Query("DELETE FROM history_cache WHERE child_id = :childId")
void deleteHistoryByChildId(String childId);
}

View File

@ -1,30 +0,0 @@
package one.nem.kidshift.data.room.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import java.util.Date;
import one.nem.kidshift.data.room.converter.DateTypeConverter;
@Entity(tableName = "history_cache")
@TypeConverters({DateTypeConverter.class})
public class HistoryCacheEntity {
@PrimaryKey
@ColumnInfo(name = "id")
@NonNull
public String id;
@ColumnInfo(name = "child_id")
public String childId;
@ColumnInfo(name = "task_id")
public String taskId;
@ColumnInfo(name = "registered_at")
public Date registeredAt;
}

View File

@ -1,18 +0,0 @@
package one.nem.kidshift.data.room.model;
import androidx.room.Embedded;
import androidx.room.Relation;
import one.nem.kidshift.data.room.entity.HistoryCacheEntity;
import one.nem.kidshift.data.room.entity.TaskCacheEntity;
public class HistoryWithTask {
@Embedded
public HistoryCacheEntity history;
@Relation(
parentColumn = "task_id",
entityColumn = "id"
)
public TaskCacheEntity task;
}

View File

@ -9,17 +9,13 @@ import javax.inject.Inject;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.data.retrofit.model.converter.HistoryModelConverter;
import one.nem.kidshift.data.room.KidShiftDatabase;
import one.nem.kidshift.data.room.entity.ChildCacheEntity;
import one.nem.kidshift.data.room.entity.TaskCacheEntity;
import one.nem.kidshift.data.room.entity.TaskChildLinkageEntity;
import one.nem.kidshift.data.room.model.HistoryWithTask;
import one.nem.kidshift.data.room.utils.converter.ChildCacheConverter;
import one.nem.kidshift.data.room.utils.converter.HistoryCacheConverter;
import one.nem.kidshift.data.room.utils.converter.TaskCacheConverter;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@ -43,7 +39,7 @@ public class CacheWrapper {
* @param taskList タスクリスト
* @return CompletableFuture
*/
public CompletableFuture<Void> updateChildTaskCache(List<ChildModel> childList, List<TaskItemModel> taskList) {
public CompletableFuture<Void> updateCache(List<ChildModel> childList, List<TaskItemModel> taskList) {
return CompletableFuture.runAsync(() -> {
logger.debug("Updating cache");
insertChildList(childList).join();
@ -69,12 +65,6 @@ public class CacheWrapper {
});
}
public CompletableFuture<Void> updateHistoryCache(List<HistoryModel> historyList) {
return CompletableFuture.runAsync(() -> {
kidShiftDatabase.historyCacheDao().insertHistoryList(HistoryCacheConverter.historyModelListToHistoryCacheEntityList(historyList));
});
}
/**
* 子供リストをDBに挿入する
* @param childList 子供リスト
@ -103,8 +93,8 @@ public class CacheWrapper {
*/
public CompletableFuture<List<ChildModel>> getChildList() {
return CompletableFuture.supplyAsync(() -> {
List<ChildCacheEntity> result = kidShiftDatabase.childCacheDao().getChildList();
return ChildCacheConverter.childCacheEntityListToChildModelList(result);
// Get a list of children from the database
return null;
});
}
@ -119,12 +109,5 @@ public class CacheWrapper {
});
}
public CompletableFuture<List<HistoryModel>> getHistoryList(String childId) {
return CompletableFuture.supplyAsync(() -> {
List<HistoryWithTask> result = kidShiftDatabase.historyCacheDao().getHistoryWithTasksByChildId(childId);
return HistoryCacheConverter.historyWithTaskListToHistoryModelList(result);
});
}
}

View File

@ -38,12 +38,4 @@ public class ChildCacheConverter {
public static ChildModel childCacheEntityToChildModel(ChildCacheEntity entity) {
return new ChildModel(entity.id, entity.name);
}
public static List<ChildModel> childCacheEntityListToChildModelList(List<ChildCacheEntity> result) {
List<ChildModel> childList = new ArrayList<>();
for (ChildCacheEntity entity : result) {
childList.add(childCacheEntityToChildModel(entity));
}
return childList;
}
}

View File

@ -1,60 +0,0 @@
package one.nem.kidshift.data.room.utils.converter;
import java.util.ArrayList;
import java.util.List;
import one.nem.kidshift.data.room.entity.HistoryCacheEntity;
import one.nem.kidshift.data.room.model.HistoryWithTask;
import one.nem.kidshift.model.HistoryModel;
public class HistoryCacheConverter {
public static HistoryCacheEntity historyModelToHistoryCacheEntity(HistoryModel historyModel) {
HistoryCacheEntity historyCacheEntity = new HistoryCacheEntity();
historyCacheEntity.id = historyModel.getId();
historyCacheEntity.taskId = historyModel.getTaskId();
historyCacheEntity.childId = historyModel.getChildId();
historyCacheEntity.registeredAt = historyModel.getRegisteredAt();
return historyCacheEntity;
}
public static HistoryModel historyCacheEntityToHistoryModel(HistoryCacheEntity historyCacheEntity) {
HistoryModel historyModel = new HistoryModel();
historyModel.setId(historyCacheEntity.id);
historyModel.setTaskId(historyCacheEntity.taskId);
historyModel.setChildId(historyCacheEntity.childId);
historyModel.setRegisteredAt(historyCacheEntity.registeredAt);
return historyModel;
}
public static List<HistoryCacheEntity> historyModelListToHistoryCacheEntityList(List<HistoryModel> historyModelList) {
List<HistoryCacheEntity> historyCacheEntityList = new ArrayList<>();
for (HistoryModel historyModel : historyModelList) {
historyCacheEntityList.add(historyModelToHistoryCacheEntity(historyModel));
}
return historyCacheEntityList;
}
public static List<HistoryModel> historyCacheEntityListToHistoryModelList(List<HistoryCacheEntity> historyCacheEntityList) {
List<HistoryModel> historyModelList = new ArrayList<>();
for (HistoryCacheEntity historyCacheEntity : historyCacheEntityList) {
historyModelList.add(historyCacheEntityToHistoryModel(historyCacheEntity));
}
return historyModelList;
}
public static List<HistoryModel> historyWithTaskListToHistoryModelList(List<HistoryWithTask> result) {
List<HistoryModel> historyModelList = new ArrayList<>();
for (HistoryWithTask historyWithTask : result) {
HistoryModel historyModel = new HistoryModel();
historyModel.setId(historyWithTask.history.id);
historyModel.setTaskId(historyWithTask.history.taskId);
historyModel.setChildId(historyWithTask.history.childId);
historyModel.setRegisteredAt(historyWithTask.history.registeredAt);
historyModel.setTaskName(historyWithTask.task.name);
historyModel.setReward(historyWithTask.task.reward);
historyModelList.add(historyModel);
}
return historyModelList;
}
}

View File

@ -37,14 +37,7 @@ public class TaskCacheConverter {
* @return TaskItemModel
*/
public static TaskItemModel taskCacheEntityToTaskModel(TaskCacheEntity entity) {
TaskItemModel model = new TaskItemModel();
model.setId(entity.id);
model.setName(entity.name);
model.setReward(entity.reward);
model.setIconEmoji(entity.iconEmoji == null ? "" : entity.iconEmoji); // Workaround
model.setBgColor(null); // TODO: 実装
model.setAttachedChildren(null); // TODO: 実装
return model;
return new TaskItemModel(entity.id, entity.name, entity.iconEmoji, entity.reward, null);
}
/**

View File

@ -32,8 +32,6 @@ dependencies {
implementation libs.material
implementation libs.navigation.fragment
implementation libs.navigation.ui
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
@ -50,5 +48,4 @@ dependencies {
implementation project(':model')
implementation project(':utils')
implementation project(':data')
implementation project(':shared')
}

View File

@ -1,10 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".ChildManageMainActivity"
android:exported="false" />
</application>
</manifest>

View File

@ -90,24 +90,24 @@ public class ChildMainFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//
// logger.addTag("ChildMainFragment");
//
// Integer reward = rewardData.getTotalReward().join();
//
// logger.debug("取得したデータ: " + reward);
//
// Calendar cl = Calendar.getInstance();
// TextView tr = view.findViewById(R.id.totalReward);
// TextView dv = view.findViewById(R.id.dateView);
// Date date = new Date();
//
//
// NumberFormat nf = NumberFormat.getNumberInstance();
// SimpleDateFormat sdf = new SimpleDateFormat();
// sdf.applyPattern("yyyy年MM月");
//
// dv.setText(sdf.format(cl.getTime()) + " お小遣い総額");
// tr.setText("¥" + nf.format(reward).toString());
logger.addTag("ChildMainFragment");
Integer reward = rewardData.getTotalReward().join();
logger.debug("取得したデータ: " + reward);
Calendar cl = Calendar.getInstance();
TextView tr = view.findViewById(R.id.totalReward);
TextView dv = view.findViewById(R.id.dateView);
Date date = new Date();
NumberFormat nf = NumberFormat.getNumberInstance();
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("yyyy年MM月");
dv.setText(sdf.format(cl.getTime()) + " お小遣い総額");
tr.setText("¥" + nf.format(reward).toString());
}
}

View File

@ -1,201 +0,0 @@
package one.nem.kidshift.feature.child;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.print.PrintAttributes;
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.feature.child.adapter.ChildListAdapter;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.callback.ChildModelCallback;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint
public class ChildManageMainActivity extends AppCompatActivity {
@Inject
ChildData childData;
@Inject
KSLoggerFactory loggerFactory;
private KSLogger logger;
ChildListAdapter childListAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_child_manage_main);
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;
});
logger = loggerFactory.create("ChildManageMainActivity");
// ToolBarのセットアップ
Toolbar toolbar = findViewById(R.id.toolBar);
// タイトル
toolbar.setTitle("子供アカウント管理");
// 閉じる
toolbar.setNavigationIcon(one.nem.kidshift.shared.R.drawable.close_24px);
toolbar.setNavigationOnClickListener(v -> finish());
// 追加ボタン
toolbar.inflateMenu(R.menu.child_manage_main_toolbar_item);
toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.add_child_account) {
showAddChildDialog();
return true;
}
return false;
});
RecyclerView recyclerView = findViewById(R.id.childListRecyclerView);
childListAdapter = new ChildListAdapter();
childListAdapter.setButtonEventCallback(new ChildListAdapter.ButtonEventCallback() {
@Override
public void onEditButtonClick(ChildModel childModel) {
showEditChildDialog(childModel);
}
@Override
public void onLoginButtonClick(ChildModel childModel) {
childData.issueLoginCode(childModel.getId()).thenAccept(loginCode -> {
logger.debug("ログインコード発行完了: " + loginCode);
runOnUiThread(() -> showLoginCodeDialog(loginCode));
});
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(childListAdapter);
updateList();
}
private void showLoginCodeDialog(Integer loginCode) {
View view = getLayoutInflater().inflate(R.layout.child_login_code_dialog_layout, null);
TextView loginCodeTextView = view.findViewById(R.id.loginCode);
// loginCodeをStringに変換して4桁 2つに分割してハイフンで繋げる
loginCodeTextView.setText(loginCode.toString().substring(0, 4) + "-" + loginCode.toString().substring(4));
new MaterialAlertDialogBuilder(this)
.setTitle("ログインコード")
.setView(view)
.setPositiveButton("閉じる", (dialog, which) -> dialog.dismiss())
.show();
}
private void showAddChildDialog() {
// EditTextを作成
EditText childNameEditText = new EditText(this);
childNameEditText.setHint("子供の名前");
// FrameLayoutに入れる
FrameLayout container = new FrameLayout(this);
container.addView(childNameEditText);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) childNameEditText.getLayoutParams();
params.setMargins(32, 16, 32, 16);
childNameEditText.setLayoutParams(params);
new MaterialAlertDialogBuilder(this)
.setTitle("子供アカウント追加")
.setView(container)
.setPositiveButton("追加", (dialog, which) -> {
String childName = Objects.requireNonNull(childNameEditText.getText()).toString();
if (childName.isEmpty()) {
Toast.makeText(this, "名前を入力してください", Toast.LENGTH_SHORT).show();
}
childData.addChild(new ChildModel(childName))
.thenRun(this::updateListDirectly);
})
.setNegativeButton("キャンセル", (dialog, which) -> dialog.dismiss())
.show();
}
private void showEditChildDialog(ChildModel childModel) {
// EditTextを作成
EditText childNameEditText = new EditText(this);
childNameEditText.setHint("子供の名前");
childNameEditText.setText(childModel.getName());
// FrameLayoutに入れる
FrameLayout container = new FrameLayout(this);
container.addView(childNameEditText);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) childNameEditText.getLayoutParams();
params.setMargins(32, 16, 32, 16);
childNameEditText.setLayoutParams(params);
new MaterialAlertDialogBuilder(this)
.setTitle("子供アカウント編集")
.setView(container)
.setPositiveButton("保存", (dialog, which) -> {
String childName = Objects.requireNonNull(childNameEditText.getText()).toString();
if (childName.isEmpty()) {
Toast.makeText(this, "名前を入力してください", Toast.LENGTH_SHORT).show();
}
childModel.setName(childName);
childData.updateChild(childModel)
.thenRun(this::updateListDirectly);
})
.setNegativeButton("キャンセル", (dialog, which) -> dialog.dismiss())
// 削除ボタン
.setNeutralButton("削除", (dialog, which) -> { // TODO: 確認ダイアログを表示する
childData.removeChild(childModel.getId())
.thenRun(this::updateListDirectly);
})
.show();
}
@SuppressLint("NotifyDataSetChanged")
private void updateList() {
childData.getChildList(new ChildModelCallback() {
@Override
public void onUnchanged() {
}
@Override
public void onUpdated(List<ChildModel> childModelList) {
}
@Override
public void onFailed(String message) {
}
}).thenAccept(childListAdapter::setChildList).thenRun(() -> {
runOnUiThread(() -> childListAdapter.notifyDataSetChanged());
});
}
@SuppressLint("NotifyDataSetChanged")
private void updateListDirectly() {
childData.getChildListDirect().thenAccept(childListAdapter::setChildList).thenRun(() -> {
runOnUiThread(() -> childListAdapter.notifyDataSetChanged());
});
}
}

View File

@ -1,79 +0,0 @@
package one.nem.kidshift.feature.child.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import one.nem.kidshift.feature.child.R;
import one.nem.kidshift.model.ChildModel;
public class ChildListAdapter extends RecyclerView.Adapter<ChildListAdapter.ViewHolder>{
private List<ChildModel> childList;
private ButtonEventCallback buttonEventCallback;
public ChildListAdapter() {
}
public void setChildList(List<ChildModel> childList) {
this.childList = childList;
}
public void setButtonEventCallback(ButtonEventCallback buttonEventCallback) {
this.buttonEventCallback = buttonEventCallback;
}
@NonNull
@Override
public ChildListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_child_manage_main, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ChildListAdapter.ViewHolder holder, int position) {
ChildModel childModel = childList.get(position);
holder.childNameTextView.setText(childModel.getName());
holder.editButton.setOnClickListener(v -> {
if (buttonEventCallback != null) {
buttonEventCallback.onEditButtonClick(childModel);
}
});
holder.loginButton.setOnClickListener(v -> {
if (buttonEventCallback != null) {
buttonEventCallback.onLoginButtonClick(childModel);
}
});
}
@Override
public int getItemCount() {
return childList == null ? 0 : childList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView childNameTextView;
Button editButton;
Button loginButton;
public ViewHolder(@NonNull View itemView) {
super(itemView);
childNameTextView = itemView.findViewById(R.id.childNameTextView);
editButton = itemView.findViewById(R.id.editButton);
loginButton = itemView.findViewById(R.id.loginButton);
}
}
public interface ButtonEventCallback {
void onEditButtonClick(ChildModel childModel);
void onLoginButtonClick(ChildModel childModel);
}
}

View File

@ -1,33 +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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChildManageMainActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/childListRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolBar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/loginCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="30dp"
android:paddingBottom="25dp"
android:textAppearance="@style/TextAppearance.AppCompat.Display3" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,47 +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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="12dp"
android:layout_marginHorizontal="16dp">
<TextView
android:id="@+id/childNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Placeholder"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/linearLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/editButton"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="編集"
android:layout_marginRight="8px"/>
<Button
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="ログイン"
android:layout_marginLeft="8px"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_child_account"
android:icon="@drawable/add_24px"
app:showAsAction="ifRoom"
android:title="子供アカウントの追加" />
</menu>

View File

@ -1 +0,0 @@
/build

View File

@ -1,52 +0,0 @@
plugins {
alias(libs.plugins.androidLibrary)
id 'com.google.dagger.hilt.android'
id 'androidx.navigation.safeargs'
}
android {
namespace 'one.nem.kidshift.feature.common'
compileSdk 34
defaultConfig {
minSdk 28
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.navigation.fragment
implementation libs.navigation.ui
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation libs.androidx.swiperefreshlayout
// Hilt (DI)
implementation libs.com.google.dagger.hilt.android
annotationProcessor libs.com.google.dagger.hilt.compiler
implementation 'one.nem:compact-calendar-view:1.0.0'
// Project modules
implementation project(':model')
implementation project(':utils')
implementation project(':data')
implementation project(':shared')
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,26 +0,0 @@
package one.nem.kidshift.feature.common;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("one.nem.kidshift.feature.common.test", appContext.getPackageName());
}
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,470 +0,0 @@
package one.nem.kidshift.feature.common;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.view.MenuHost;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.sundeepk.compactcalendarview.CompactCalendarView;
import com.github.sundeepk.compactcalendarview.domain.Event;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.TaskData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.feature.common.adapter.ChildListItemAdapter;
import one.nem.kidshift.feature.common.adapter.TaskListItemAdapter;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.model.callback.TaskItemModelCallback;
import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.RecyclerViewAnimUtils;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
import one.nem.kidshift.utils.models.FabEventCallback;
@AndroidEntryPoint
public class CommonHomeFragment extends Fragment {
private static final String ARG_IS_CHILD_MODE = "isChildMode";
private static final String ARG_CHILD_ID = "childId";
@Inject
KSLoggerFactory ksLoggerFactory;
@Inject
TaskData taskData;
@Inject
ChildData childData;
@Inject
FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject
RewardData rewardData;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils;
@Inject
UserSettings userSettings;
private boolean isChildMode;
private String childId;
private KSLogger logger;
CompactCalendarView compactCalendarView;
View calendarContainer;
SwipeRefreshLayout swipeRefreshLayout;
TaskListItemAdapter taskListItemAdapter;
TextView calendarTitleTextView;
ImageButton calendarPrevButton;
ImageButton calendarNextButton;
public CommonHomeFragment() {
// Required empty public constructor
}
// TODO: SwipeToRef
public static CommonHomeFragment newInstance(boolean isChildMode, String childId) {
CommonHomeFragment fragment = new CommonHomeFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_IS_CHILD_MODE, isChildMode);
args.putString(ARG_CHILD_ID, childId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logger = ksLoggerFactory.create("CommonHomeFragment");
if (userSettings.getAppCommonSetting().isChildMode()) {
logger.debug("子供モードで起動(子供ログイン)");
isChildMode = true;
childId = userSettings.getAppCommonSetting().getChildId();
logger.debug("childId: " + childId);
} else {
if (getArguments() != null) {
isChildMode = getArguments().getBoolean(ARG_IS_CHILD_MODE) && getArguments().getBoolean(ARG_IS_CHILD_MODE);
childId = getArguments().getString(ARG_CHILD_ID) != null ? getArguments().getString(ARG_CHILD_ID) : "";
}
if (childId != null && !childId.isEmpty()) {
isChildMode = true;
}
if (isChildMode) {
logger.debug("子供モードで起動");
logger.debug("childId: " + childId);
} else {
logger.debug("保護者モードで起動");
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_common_home, container, false);
compactCalendarView = view.findViewById(R.id.calendar);
taskListItemAdapter = new TaskListItemAdapter();
taskListItemAdapter.setCallback((taskId, taskName) -> {
if (isChildMode) {
showConfirmDialog(taskId, taskName);
} else {
showChildSelectDialog(taskId, taskName);
}
});
RecyclerView taskListRecyclerView = view.findViewById(R.id.taskListRecyclerView);
taskListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
taskListRecyclerView.setAdapter(taskListItemAdapter);
taskListRecyclerView.setItemViewCacheSize(10);
recyclerViewAnimUtils.setSlideUpAnimation(taskListRecyclerView);
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(this::updateData);
calendarTitleTextView = view.findViewById(R.id.calendarTitleTextView);
calendarPrevButton = view.findViewById(R.id.calendarPrevButton);
calendarNextButton = view.findViewById(R.id.calendarNextButton);
calendarContainer = view.findViewById(R.id.calendarContainer);
initCalender();
updateData();
return view;
}
private void recyclerViewRefresh() {
requireActivity().runOnUiThread(() -> {
taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
taskListItemAdapter.notifyItemRangeInserted(0, taskListItemAdapter.getItemCount());
});
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
if (isChildMode) {
setupFabChild();
} else {
setupFabParent();
}
setupToolBar();
}
/**
* 親モードの場合(=子供モードではない場合)のFABの設定
*/
private void setupFabParent() {
fabManager.show();
fabManager.setFabEventCallback(new FabEventCallback() {
@Override
public void onClicked() {
showAddTaskDialog();
}
@Override
public void onLongClicked() {
// Do nothing
}
});
}
/**
* 子供モードの場合のFABの設定
*/
private void setupFabChild() {
fabManager.hide();
}
private void setupToolBar() {
if (isChildMode) {
toolBarManager.setTitle("ホーム");
toolBarManager.setSubtitle("子供ビュー");
} else {
toolBarManager.setTitle("ホーム");
toolBarManager.setSubtitle("保護者ビュー");
}
MenuHost menuHost = requireActivity();
menuHost.invalidateMenu();
menuHost.addMenuProvider(new MenuProvider() {
@Override
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
logger.debug("onCreateMenu, インフレート");
menu.clear();
menuInflater.inflate(R.menu.common_home_toolbar_menu, menu);
}
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
if (menuItem.getItemId() == R.id.toggle_calendar) {
if (calendarContainer.getVisibility() == View.VISIBLE) {
Animation slideUp = AnimationUtils.loadAnimation(getContext(), one.nem.kidshift.shared.R.anim.slide_up);
calendarContainer.startAnimation(slideUp);
slideUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
recyclerViewRefresh();
}
@Override
public void onAnimationEnd(Animation animation) {
calendarContainer.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
} else {
Animation slideDown = AnimationUtils.loadAnimation(getContext(), one.nem.kidshift.shared.R.anim.slide_down);
calendarContainer.startAnimation(slideDown);
slideDown.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
calendarContainer.setVisibility(View.VISIBLE);
recyclerViewRefresh();
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return true;
}
return false;
}
}, this.getViewLifecycleOwner(), Lifecycle.State.RESUMED);
}
/**
* タスク完了確認ダイアログを表示 (子供モード用)
*
* @param taskId タスクID
* @param taskName タスク名
*/
private void showConfirmDialog(String taskId, String taskName) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスクを完了しますか?")
.setMessage(taskName + "を完了しますか?")
.setPositiveButton("はい", (dialog, which) -> {
dialog.dismiss();
taskData.recordTaskCompletion(taskId, childId);
})
.setNegativeButton("いいえ", (dialog, which) -> {
dialog.dismiss();
taskData.recordTaskCompletion(taskId, childId);
})
.show();
}
/**
* タスク完了ダイアログ(子供選択画面)を表示 (親モード用)
*
* @param taskId タスクID
* @param taskName タスク名
*/
private void showChildSelectDialog(String taskId, String taskName) { // TODO: Assignされている子供かどうかを考慮するように
RecyclerView childListRecyclerView = new RecyclerView(requireContext());
childListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
childListRecyclerView.setPadding(0, 48, 0, 0);
// TODO: キャッシュから取得する方にする
childData.getChildListDirect().thenAccept(childModelList -> {
ChildListItemAdapter childListItemAdapter = new ChildListItemAdapter(childModelList);
childListItemAdapter.setCallback(childId -> {
taskData.recordTaskCompletion(taskId, childId);
});
childListRecyclerView.setAdapter(childListItemAdapter);
}).thenRun(() -> {
requireActivity().runOnUiThread(() -> {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(taskName + "を完了したお子様を選択")
.setView(childListRecyclerView)
.setNeutralButton("閉じる", (dialog, which) -> dialog.dismiss())
.show();
});
});
}
/**
* タスク情報を更新
* @return CompletableFuture<Void>
*/
@SuppressLint("NotifyDataSetChanged")
private CompletableFuture<Void> updateTaskInfo() { // TODO: updatedの場合の処理など実装
return taskData.getTasks(new TaskItemModelCallback() {
@Override
public void onUnchanged() {
// Do nothing
}
@Override
public void onUpdated(List<TaskItemModel> taskItem) { // Workaround
taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
taskListItemAdapter.setTaskItemModelList(taskItem);
taskListItemAdapter.notifyItemRangeInserted(0, taskItem.size());
}
@Override
public void onFailed(String message) {
// TODO: ユーザーに丁寧に通知
Toast.makeText(requireContext(), "タスク情報の取得に失敗しました", Toast.LENGTH_SHORT).show(); // Workaround
}
}).thenAccept(taskItemModel -> {
requireActivity().runOnUiThread(() -> {
// taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
// taskListItemAdapter.setTaskItemModelList(taskItemModel);
// taskListItemAdapter.notifyItemRangeInserted(0, taskItemModel.size());
taskListItemAdapter.setTaskItemModelList(taskItemModel);
taskListItemAdapter.notifyItemRangeChanged(0, taskItemModel.size());
});
});
}
/**
* カレンダーを更新
* @return CompletableFuture<Void>
*/
private CompletableFuture<Void> updateCalender() {
return rewardData.getRewardHistoryList().thenAccept(historyModels -> {
historyModels.forEach(historyModel -> {
compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel)); // debug
});
});
}
private void initCalender() {
compactCalendarView.setListener(new CompactCalendarView.CompactCalendarViewListener() {
@Override
public void onDayClick(Date date) { // Test
List<Event> events = compactCalendarView.getEvents(date);
ScrollView scrollView = new ScrollView(requireContext());
LinearLayout linearLayout = new LinearLayout(requireContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setPadding(96, 24, 96, 24);
scrollView.addView(linearLayout);
events.forEach(event -> {
TextView textView = new TextView(requireContext());
textView.setText(((HistoryModel) event.getData()).getTaskName() + " @ " + ((HistoryModel) event.getData()).getChildId());
textView.setPadding(0, 0, 0, 24);
linearLayout.addView(textView);
});
new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスク一覧 (DEBUG)")
.setMessage(events.size() + "件のタスクが登録されています")
.setView(scrollView)
.setNeutralButton("閉じる", (dialog, which) -> dialog.dismiss())
.show();
}
@Override
public void onMonthScroll(Date date) {
// 0000年00月の形式に変換 getYear/getMonthは非推奨
calendarTitleTextView.setText(String.format("%d年%d月", date.getYear() + 1900, date.getMonth() + 1)); // 統合
}
});
// 初回
Date date = new Date();
calendarTitleTextView.setText(String.format("%d年%d月", date.getYear() + 1900, date.getMonth() + 1)); // 統合
calendarPrevButton.setOnClickListener(v -> {
compactCalendarView.scrollLeft();
});
calendarNextButton.setOnClickListener(v -> {
compactCalendarView.scrollRight();
});
}
/**
* データを更新 (updateTaskInfoとupdateCalenderを並列実行)
*/
private void updateData() {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(true);
});
CompletableFuture.allOf(updateTaskInfo(), updateCalender()).thenRun(() -> {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false);
});
});
}
/**
* タスク追加ダイアログを表示
*/
private void showAddTaskDialog() {
View view = getLayoutInflater().inflate(R.layout.common_task_add_dialog_layout, null);
view.setPadding(48, 24, 48, 24);
new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスクを追加")
.setView(view)
.setPositiveButton("追加", (dialog, which) -> {
EditText taskNameEditText = view.findViewById(R.id.addTaskNameEditText);
EditText taskRewardEditText = view.findViewById(R.id.addTaskRewardEditText);
TaskItemModel taskItemModel = new TaskItemModel();
taskItemModel.setName(taskNameEditText.getText().toString());
taskItemModel.setReward(Integer.parseInt(taskRewardEditText.getText().toString()));
taskData.addTask(taskItemModel).thenRun(this::updateData);
})
.setNegativeButton("キャンセル", (dialog, which) -> dialog.dismiss())
.show();
}
}

View File

@ -1,90 +0,0 @@
package one.nem.kidshift.feature.common;
import static androidx.navigation.Navigation.findNavController;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.feature.common.adapter.SelectShowChildListItemAdapter;
import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.RecyclerViewAnimUtils;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint
public class CommonSelectChildFragment extends Fragment {
@Inject
KSLoggerFactory loggerFactory;
@Inject
ChildData childData;
@Inject
FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils;
private KSLogger logger;
private SelectShowChildListItemAdapter adapter;
public CommonSelectChildFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logger = loggerFactory.create("CommonSelectChildFragment");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_common_select_child, container, false);
RecyclerView childListRecyclerView = view.findViewById(R.id.selectShowChildListRecyclerView);
childListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerViewAnimUtils.setSlideUpAnimation(childListRecyclerView);
adapter = new SelectShowChildListItemAdapter();
adapter.setCallback(childId -> {
// 静的解析エラーが発生するのになぜか実行はできる
findNavController(view).navigate(CommonSelectChildFragmentDirections.actionCommonSelectChildFragmentToCommonHomeFragmentParentChild(childId));
});
CompletableFuture.runAsync(() -> childListRecyclerView.setAdapter(adapter)).thenRun(() -> childData.getChildListDirect().thenAccept(childList -> {
requireActivity().runOnUiThread(() -> {
adapter.setChildDataList(childList);
adapter.notifyItemRangeChanged(0, childList.size());
});
}).exceptionally(e -> {
logger.error("Failed to load child list");
return null;
}));
return view;
}
@Override
public void onResume() {
super.onResume();
fabManager.hide();
toolBarManager.setTitle("子供選択");
toolBarManager.setSubtitle(null);
}
}

View File

@ -1,75 +0,0 @@
package one.nem.kidshift.feature.common.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import one.nem.kidshift.feature.common.R;
import one.nem.kidshift.model.ChildModel;
public class ChildListItemAdapter extends RecyclerView.Adapter<ChildListItemAdapter.ViewHolder> {
private List<ChildModel> childDataList;
private CompleteButtonClickedCallback callback;
public ChildListItemAdapter() {
// Empty constructor
}
public ChildListItemAdapter(List<ChildModel> childDataList) {
this.childDataList = childDataList;
}
public void setChildDataList(List<ChildModel> childDataList) {
this.childDataList = childDataList;
}
public void setCallback(CompleteButtonClickedCallback callback) {
this.callback = callback;
}
@NonNull
@Override
public ChildListItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_task_completion_child, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ChildListItemAdapter.ViewHolder holder, int position) {
ChildModel childData = childDataList.get(position);
holder.childName.setText(childData.getName());
holder.completedButton.setOnClickListener(v -> {
if (callback != null) {
callback.onClicked(childData.getId());
}
});
}
@Override
public int getItemCount() {
return childDataList == null ? 0 : childDataList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView childName;
Button completedButton;
public ViewHolder(@NonNull android.view.View itemView) {
super(itemView);
childName = itemView.findViewById(R.id.childNameTextView);
completedButton = itemView.findViewById(R.id.completeButton);
}
}
public interface CompleteButtonClickedCallback {
void onClicked(String taskId);
}
}

View File

@ -1,75 +0,0 @@
package one.nem.kidshift.feature.common.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import one.nem.kidshift.feature.common.R;
import one.nem.kidshift.model.ChildModel;
public class SelectShowChildListItemAdapter extends RecyclerView.Adapter<SelectShowChildListItemAdapter.ViewHolder> {
private List<ChildModel> childDataList;
private CompleteButtonClickedCallback callback;
public SelectShowChildListItemAdapter() {
// Empty constructor
}
public SelectShowChildListItemAdapter(List<ChildModel> childDataList) {
this.childDataList = childDataList;
}
public void setChildDataList(List<ChildModel> childDataList) {
this.childDataList = childDataList;
}
public void setCallback(CompleteButtonClickedCallback callback) {
this.callback = callback;
}
@NonNull
@Override
public SelectShowChildListItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_select_show_child, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SelectShowChildListItemAdapter.ViewHolder holder, int position) {
ChildModel childData = childDataList.get(position);
holder.childName.setText(childData.getName());
holder.selectButton.setOnClickListener(v -> {
if (callback != null) {
callback.onClicked(childData.getId());
}
});
}
@Override
public int getItemCount() {
return childDataList == null ? 0 : childDataList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView childName;
Button selectButton;
public ViewHolder(@NonNull android.view.View itemView) {
super(itemView);
childName = itemView.findViewById(R.id.childNameTextView);
selectButton = itemView.findViewById(R.id.selectButton);
}
}
public interface CompleteButtonClickedCallback {
void onClicked(String taskId);
}
}

View File

@ -1,80 +0,0 @@
package one.nem.kidshift.feature.common.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import one.nem.kidshift.feature.common.R;
import one.nem.kidshift.model.tasks.TaskItemModel;
public class TaskListItemAdapter extends RecyclerView.Adapter<TaskListItemAdapter.ViewHolder> {
private List<TaskItemModel> taskItemModelList;
private CompleteButtonClickedCallback callback;
public TaskListItemAdapter() {
}
public void setTaskItemModelList(List<TaskItemModel> taskItemModelList) {
this.taskItemModelList = taskItemModelList;
}
public void setCallback(CompleteButtonClickedCallback callback) {
this.callback = callback;
}
@NonNull
@Override
public TaskListItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// View view = View.inflate(parent.getContext(), R.layout.list_item_common_task, null);
// Workaround? by https://stackoverflow.com/questions/30691150/match-parent-width-does-not-work-in-recyclerview
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_common_task, parent, false);
return new ViewHolder(view);
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull TaskListItemAdapter.ViewHolder holder, int position) {
TaskItemModel taskItemModel = taskItemModelList.get(position);
holder.taskTitle.setText(taskItemModel.getName());
holder.taskContents.setText(taskItemModel.getReward() + ""); // TODO: ハードコードやめる
holder.completedButton.setOnClickListener(v -> {
if (callback != null) {
callback.onClicked(taskItemModel.getId(), taskItemModel.getName());
}
});
}
@Override
public int getItemCount() {
return taskItemModelList == null ? 0 : taskItemModelList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView taskTitle;
TextView taskContents;
Button completedButton;
public ViewHolder(@NonNull View itemView) {
super(itemView);
taskTitle = itemView.findViewById(R.id.task_title_text_view);
taskContents = itemView.findViewById(R.id.task_contents_text_view);
completedButton = itemView.findViewById(R.id.actbutton);
}
}
public interface CompleteButtonClickedCallback {
void onClicked(String taskId, String taskName);
}
}

View File

@ -1,45 +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="wrap_content"
android:layout_margin="0dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/addTaskNameTextInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="タスク名"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addTaskNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/addTaskRewardTextInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="金額"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/addTaskNameTextInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addTaskRewardEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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=".CommonHomeFragment"
android:id="@+id/swipeRefreshLayout">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/calendarContainer">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorSurface">
<ImageButton
android:id="@+id/calendarPrevButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/arrow_back_24px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/colorOnSurface"
android:padding="8dp"/>
<TextView
android:id="@+id/calendarTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorOnSurface"
android:textSize="20sp"
android:text="2000年1月"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/calendarNextButton"
app:layout_constraintStart_toEndOf="@+id/calendarPrevButton"
app:layout_constraintTop_toTopOf="parent"/>
<ImageButton
android:id="@+id/calendarNextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/arrow_forward_24px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/colorOnSurface"
android:padding="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.github.sundeepk.compactcalendarview.CompactCalendarView
android:id="@+id/calendar"
android:layout_width="fill_parent"
android:layout_height="300dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:compactCalendarBackgroundColor="@color/colorSurface"
app:compactCalendarCurrentDayBackgroundColor="@color/colorSurfaceVariant"
app:compactCalendarCurrentSelectedDayBackgroundColor="@color/colorSurfaceVariant"
app:compactCalendarMultiEventIndicatorColor="@color/colorOnSurface"
app:compactCalendarTextColor="@color/colorOnSurface"
app:compactCalendarTextSize="12sp"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/taskListRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/calendarContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -1,30 +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=".CommonSelectChildFragment" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:text="使用するお子様を選択"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/selectShowChildListRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="128dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/textContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="@+id/task_title_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="お手伝い名"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/task_contents_text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="13dp"
android:text="円/回"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/task_title_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0">
<Button
android:id="@+id/actbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="完了"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,35 +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="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:padding="8px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/childNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="PLACEHOLDER"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<!-- ImageButtonに変更? -->
<Button
android:id="@+id/selectButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="表示" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,35 +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="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:padding="8px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/childNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="PLACEHOLDER"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<!-- ImageButtonに変更? -->
<Button
android:id="@+id/completeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="完了" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show More