Compare commits

..

1 Commits

53 changed files with 190 additions and 1123 deletions

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 # WIP
~~## メモ ## メモ
- リリース前(=提出前)には`DEBUG_ONLY`で検索してチェック(念のため) - リリース前(=提出前)には`DEBUG_ONLY`で検索してチェック(念のため)
## リリース前チェック ## リリース前チェック
- DBの破壊的マイグレーションを許可するオプションを無効に~~ - 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機種で使用されるテーマの適用が中途半端なので完全に適用するように

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -102,6 +102,11 @@ public class ChildLoginActivity extends AppCompatActivity {
startActivity(new Intent(this, MainActivity.class)); startActivity(new Intent(this, MainActivity.class));
}); });
}); });
// 親ログインボタンを押したときの処理
findViewById(R.id.toParentLoginButton).setOnClickListener(v -> {
finish();
});
} }
private String getLoginCode() { private String getLoginCode() {

View File

@ -4,8 +4,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
@ -25,7 +23,6 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI; import androidx.navigation.ui.NavigationUI;
import com.google.android.material.bottomnavigation.BottomNavigationView; 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.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.divider.MaterialDivider; import com.google.android.material.divider.MaterialDivider;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
@ -34,12 +31,8 @@ import com.google.android.material.navigation.NavigationView;
import javax.inject.Inject; import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint; 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.data.UserSettings;
import one.nem.kidshift.feature.child.ChildManageMainActivity; 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.FabManager;
import one.nem.kidshift.utils.FeatureFlag; import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
@ -59,11 +52,6 @@ public class MainActivity extends AppCompatActivity {
FeatureFlag featureFlag; FeatureFlag featureFlag;
@Inject @Inject
UserSettings userSettings; UserSettings userSettings;
@Inject
ParentData parentData;
@Inject
ChildData childData;
private KSLogger logger; private KSLogger logger;
private FloatingActionButton fab; private FloatingActionButton fab;
@ -113,9 +101,6 @@ public class MainActivity extends AppCompatActivity {
} else if (item.getItemId() == R.id.show_debug_dialog) { } else if (item.getItemId() == R.id.show_debug_dialog) {
showDebugDialog(); showDebugDialog();
return true; return true;
} else if (item.getItemId() == R.id.show_account_dialog) {
showAccountDialog();
return true;
} else { } else {
logger.warn("不明なアイテム: " + item.getItemId()); logger.warn("不明なアイテム: " + item.getItemId());
} }
@ -173,70 +158,6 @@ public class MainActivity extends AppCompatActivity {
} }
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() { private void showDebugDialog() {
ScrollView scrollView = new ScrollView(this); ScrollView scrollView = new ScrollView(this);

View File

@ -1,15 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="24" android:viewportWidth="108"
android:viewportHeight="24" android:viewportHeight="108">
android:tint="#333333"> <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">
<group android:scaleX="0.435" <aapt:attr name="android:fillColor">
android:scaleY="0.435" <gradient
android:translateX="6.78" android:endX="85.84757"
android:translateY="6.78"> 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 <path
android:fillColor="@android:color/white" android:fillColor="#FFFFFF"
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"/> android:fillType="nonZero"
</group> 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> </vector>

View File

@ -11,7 +11,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/toParentLoginButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -157,4 +157,21 @@
app:layout_constraintTop_toBottomOf="@+id/inputContainer" /> app:layout_constraintTop_toBottomOf="@+id/inputContainer" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/toParentLoginButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="200dp"
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="親ログイン"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -20,14 +20,13 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorSurface" android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme" android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:title="@string/app_name" app:title="@string/app_name" />
android:elevation="8dp" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView" android:id="@+id/fragmentContainerView"

View File

@ -2,15 +2,19 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/colorSecondary"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp"> android:padding="16dp"
android:background="?attr/colorPrimaryDark">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_foreground" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="48dp" android:text="Header Title"
android:text="KidShift"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="34sp" /> android:textSize="20sp" />
</LinearLayout> </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

@ -4,7 +4,7 @@
<!-- 保護者モード / 保護者目線 --> <!-- 保護者モード / 保護者目線 -->
<item <item
android:id="@+id/feature_common_parent_parent_navigation" android:id="@+id/feature_common_parent_parent_navigation"
android:icon="@drawable/home_24px" android:icon="@drawable/pending_24px"
android:title="ホーム" /> android:title="ホーム" />
<!-- 保護者モード / 子供目線 --> <!-- 保護者モード / 子供目線 -->
@ -16,7 +16,7 @@
<!-- 子供モード / 子供目線 --> <!-- 子供モード / 子供目線 -->
<item <item
android:id="@+id/feature_common_child_child_navigation" android:id="@+id/feature_common_child_child_navigation"
android:icon="@drawable/home_24px" android:icon="@drawable/child_care_24px"
android:title="ホーム" /> android:title="ホーム" />
<item <item

View File

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <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 <item
android:id="@+id/manage_child_account" android:id="@+id/manage_child_account"
android:icon="@drawable/manage_accounts_24px" android:icon="@drawable/manage_accounts_24px"

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

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

View File

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

View File

@ -16,7 +16,5 @@ public interface RewardData {
CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId); CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId);
CompletableFuture<Void> payReward(String historyId);
CompletableFuture<Void> payReward(List<String> historyIds);
} }

View File

@ -36,21 +36,7 @@ public class ChildDataImpl implements ChildData {
@Override @Override
public CompletableFuture<ChildModel> getChild(String childId) { public CompletableFuture<ChildModel> getChild(String childId) {
return CompletableFuture.supplyAsync(() -> { return null;
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 @Override

View File

@ -159,7 +159,7 @@ public class KSActionsImpl implements KSActions {
@Override @Override
public CompletableFuture<List<HistoryModel>> syncHistory(String childId) { public CompletableFuture<List<HistoryModel>> syncHistory(String childId) {
CompletableFuture<HistoryListResponse> callHistoryApi = CompletableFuture.supplyAsync(() -> { CompletableFuture<HistoryListResponse> callHistoryApi = CompletableFuture.supplyAsync(() -> {
Call<HistoryListResponse> call = kidShiftApiService.getHistory(childId, true); // TODO: containPaidを引数に Call<HistoryListResponse> call = kidShiftApiService.getHistory(childId);
try { try {
Response<HistoryListResponse> response = call.execute(); Response<HistoryListResponse> response = call.execute();
if (!response.isSuccessful()) { if (!response.isSuccessful()) {

View File

@ -8,26 +8,21 @@ import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.ParentData; import one.nem.kidshift.data.ParentData;
import one.nem.kidshift.data.UserSettings; import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.retrofit.KidShiftApiService; 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.ParentModel;
import one.nem.kidshift.model.callback.ParentModelCallback; import one.nem.kidshift.model.callback.ParentModelCallback;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory; import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
public class ParentDataImpl implements ParentData { public class ParentDataImpl implements ParentData {
private final UserSettings userSettings; private final UserSettings userSettings;
private final KidShiftApiService kidShiftApiService;
private final KSLogger logger; private final KSLogger logger;
private final KSActions ksActions; private final KSActions ksActions;
@Inject @Inject
public ParentDataImpl(KidShiftApiService kidShiftApiService, UserSettings userSettings, KSLoggerFactory ksLoggerFactory, KSActions ksActions) { public ParentDataImpl(KidShiftApiService kidshiftApiService, UserSettings userSettings, KSLoggerFactory ksLoggerFactory, KSActions ksActions) {
this.kidShiftApiService = kidShiftApiService;
this.userSettings = userSettings; this.userSettings = userSettings;
this.logger = ksLoggerFactory.create("ParentDataImpl"); this.logger = ksLoggerFactory.create("ParentDataImpl");
this.ksActions = ksActions; this.ksActions = ksActions;
@ -51,27 +46,8 @@ public class ParentDataImpl implements ParentData {
} }
@Override @Override
public CompletableFuture<ParentModel> getParentDirect() { public void updateParent(ParentModel parent) {
return ksActions.syncParent();
}
@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

@ -10,13 +10,11 @@ import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.KSActions; import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData; import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.UserSettings; 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.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.ChildModel; import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.HistoryModel; import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory; import one.nem.kidshift.utils.factory.KSLoggerFactory;
import retrofit2.Call;
public class RewardDataImpl implements RewardData { public class RewardDataImpl implements RewardData {
@ -25,15 +23,14 @@ public class RewardDataImpl implements RewardData {
private final CacheWrapper cacheWrapper; private final CacheWrapper cacheWrapper;
private final KSLogger logger; private final KSLogger logger;
private final ChildData childData; private final ChildData childData;
private final KidShiftApiService kidShiftApiService;
@Inject @Inject
public RewardDataImpl(KSLoggerFactory ksLoggerFactory, CacheWrapper cacheWrapper, UserSettings userSettings, KSActions ksActions, ChildData childData, KidShiftApiService kidShiftApiService) { public RewardDataImpl(KSLoggerFactory ksLoggerFactory, CacheWrapper cacheWrapper, UserSettings userSettings, KSActions ksActions, ChildData childData) {
this.userSettings = userSettings; this.userSettings = userSettings;
this.ksActions = ksActions; this.ksActions = ksActions;
this.cacheWrapper = cacheWrapper; this.cacheWrapper = cacheWrapper;
this.childData = childData; this.childData = childData;
this.kidShiftApiService = kidShiftApiService;
this.logger = ksLoggerFactory.create("RewardDataImpl"); this.logger = ksLoggerFactory.create("RewardDataImpl");
} }
@ -56,30 +53,4 @@ public class RewardDataImpl implements RewardData {
public CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId) { // TODO: localCacheを使う public CompletableFuture<List<HistoryModel>> getRewardHistoryList(String childId) { // TODO: localCacheを使う
return CompletableFuture.supplyAsync(() -> ksActions.syncHistory(childId).join()); 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

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

View File

@ -8,7 +8,6 @@ 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.ChildAuthRequest;
import one.nem.kidshift.data.retrofit.model.child.auth.ChildAuthResponse; 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.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.ParentAuthRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthResponse; 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.task.HistoryListResponse;
@ -46,15 +45,6 @@ public interface KidShiftApiService {
@POST("/parent/auth/register") @POST("/parent/auth/register")
Call<ParentAuthResponse> parentRegister(@Body ParentAuthRequest request); 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 * @param request ChildAuthRequest
@ -137,15 +127,6 @@ public interface KidShiftApiService {
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER) @Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildListResponse> getChildList(); 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 * @param request ChildAddRequest
@ -185,9 +166,6 @@ public interface KidShiftApiService {
@GET("/task/history/{childId}") @GET("/task/history/{childId}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER) @Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<HistoryListResponse> getHistory(@Path("childId") String childId, @Query("containPaid") boolean containPaid); Call<HistoryListResponse> getHistory(@Path("childId") String childId);
@POST("/task/history/{historyId}/paid")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<Void> payHistory(@Path("historyId") String historyId, @Query("isPaid") boolean isPaid);
} }

View File

@ -19,7 +19,6 @@ public class HistoryModelConverter { // TODO: JavaDoc
historyModel.setTaskId(historyResponse.getTaskId()); historyModel.setTaskId(historyResponse.getTaskId());
historyModel.setChildId(historyResponse.getChildId()); historyModel.setChildId(historyResponse.getChildId());
historyModel.setRegisteredAt(historyResponse.getRegisteredAt()); historyModel.setRegisteredAt(historyResponse.getRegisteredAt());
historyModel.setPaid(historyResponse.isPaid());
return historyModel; return historyModel;
} }
@ -29,7 +28,6 @@ public class HistoryModelConverter { // TODO: JavaDoc
historyResponse.setTaskId(historyModel.getTaskId()); historyResponse.setTaskId(historyModel.getTaskId());
historyResponse.setChildId(historyModel.getChildId()); historyResponse.setChildId(historyModel.getChildId());
historyResponse.setRegisteredAt(historyModel.getRegisteredAt()); historyResponse.setRegisteredAt(historyModel.getRegisteredAt());
historyResponse.setPaid(historyModel.isPaid());
return historyResponse; return historyResponse;
} }

View File

@ -11,7 +11,7 @@ public class ParentModelConverter {
* @return ParentModel * @return ParentModel
*/ */
public static ParentModel parentInfoResponseToParentModel(ParentInfoResponse parentInfoResponse) { 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

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

@ -7,14 +7,12 @@ public class HistoryBaseItem {
private String taskId; private String taskId;
private String childId; private String childId;
private Date registeredAt; private Date registeredAt;
private boolean isPaid;
public HistoryBaseItem(String id, String taskId, String childId, Date registeredAt, boolean isPaid) { public HistoryBaseItem(String id, String taskId, String childId, Date registeredAt) {
this.id = id; this.id = id;
this.taskId = taskId; this.taskId = taskId;
this.childId = childId; this.childId = childId;
this.registeredAt = registeredAt; this.registeredAt = registeredAt;
this.isPaid = isPaid;
} }
public HistoryBaseItem() { public HistoryBaseItem() {
@ -51,12 +49,4 @@ public class HistoryBaseItem {
public void setRegisteredAt(Date registeredAt) { public void setRegisteredAt(Date registeredAt) {
this.registeredAt = registeredAt; this.registeredAt = registeredAt;
} }
public boolean isPaid() {
return isPaid;
}
public void setPaid(boolean isPaid) {
this.isPaid = isPaid;
}
} }

View File

@ -8,7 +8,6 @@ import androidx.annotation.NonNull;
import androidx.core.view.MenuHost; import androidx.core.view.MenuHost;
import androidx.core.view.MenuProvider; import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@ -22,10 +21,7 @@ import android.view.ViewGroup;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -45,10 +41,8 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData; import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.RewardData; import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.TaskData; 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.ChildListItemAdapter;
import one.nem.kidshift.feature.common.adapter.TaskListItemAdapter; 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.callback.TaskItemModelCallback;
import one.nem.kidshift.model.tasks.TaskItemModel; import one.nem.kidshift.model.tasks.TaskItemModel;
import one.nem.kidshift.utils.FabManager; import one.nem.kidshift.utils.FabManager;
@ -78,8 +72,6 @@ public class CommonHomeFragment extends Fragment {
RewardData rewardData; RewardData rewardData;
@Inject @Inject
RecyclerViewAnimUtils recyclerViewAnimUtils; RecyclerViewAnimUtils recyclerViewAnimUtils;
@Inject
UserSettings userSettings;
private boolean isChildMode; private boolean isChildMode;
@ -87,7 +79,6 @@ public class CommonHomeFragment extends Fragment {
private KSLogger logger; private KSLogger logger;
CompactCalendarView compactCalendarView; CompactCalendarView compactCalendarView;
View calendarContainer;
SwipeRefreshLayout swipeRefreshLayout; SwipeRefreshLayout swipeRefreshLayout;
TaskListItemAdapter taskListItemAdapter; TaskListItemAdapter taskListItemAdapter;
TextView calendarTitleTextView; TextView calendarTitleTextView;
@ -104,7 +95,9 @@ public class CommonHomeFragment extends Fragment {
CommonHomeFragment fragment = new CommonHomeFragment(); CommonHomeFragment fragment = new CommonHomeFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean(ARG_IS_CHILD_MODE, isChildMode); args.putBoolean(ARG_IS_CHILD_MODE, isChildMode);
if (isChildMode) {
args.putString(ARG_CHILD_ID, childId); args.putString(ARG_CHILD_ID, childId);
}
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@ -112,30 +105,16 @@ public class CommonHomeFragment extends Fragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getArguments() != null) {
isChildMode = getArguments().getBoolean(ARG_IS_CHILD_MODE);
childId = getArguments().getString(ARG_CHILD_ID);
}
logger = ksLoggerFactory.create("CommonHomeFragment"); 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) { if (isChildMode) {
logger.debug("子供モードで起動"); logger.info("Child mode, childId: " + childId);
logger.debug("childId: " + childId);
} else { } else {
logger.debug("保護者モードで起動"); logger.info("Parent mode");
}
} }
} }
@ -149,7 +128,9 @@ public class CommonHomeFragment extends Fragment {
taskListItemAdapter = new TaskListItemAdapter(); taskListItemAdapter = new TaskListItemAdapter();
taskListItemAdapter.setCallback((taskId, taskName) -> { taskListItemAdapter.setCallback((taskId, taskName) -> {
if (isChildMode) { if (isChildMode) {
showConfirmDialog(taskId, taskName); if (showConfirmDialog(taskName)) {
taskData.recordTaskCompletion(taskId, childId);
}
} else { } else {
showChildSelectDialog(taskId, taskName); showChildSelectDialog(taskId, taskName);
} }
@ -158,7 +139,6 @@ public class CommonHomeFragment extends Fragment {
RecyclerView taskListRecyclerView = view.findViewById(R.id.taskListRecyclerView); RecyclerView taskListRecyclerView = view.findViewById(R.id.taskListRecyclerView);
taskListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); taskListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
taskListRecyclerView.setAdapter(taskListItemAdapter); taskListRecyclerView.setAdapter(taskListItemAdapter);
taskListRecyclerView.setItemViewCacheSize(10);
recyclerViewAnimUtils.setSlideUpAnimation(taskListRecyclerView); recyclerViewAnimUtils.setSlideUpAnimation(taskListRecyclerView);
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
@ -167,7 +147,63 @@ public class CommonHomeFragment extends Fragment {
calendarTitleTextView = view.findViewById(R.id.calendarTitleTextView); calendarTitleTextView = view.findViewById(R.id.calendarTitleTextView);
calendarPrevButton = view.findViewById(R.id.calendarPrevButton); calendarPrevButton = view.findViewById(R.id.calendarPrevButton);
calendarNextButton = view.findViewById(R.id.calendarNextButton); calendarNextButton = view.findViewById(R.id.calendarNextButton);
calendarContainer = view.findViewById(R.id.calendarContainer);
initCalender();
MenuHost menuHost = requireActivity();
menuHost.addMenuProvider(new MenuProvider() {
@Override
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
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) {
View calendarContainer = view.findViewById(R.id.calendarContainer);
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;
}
});
initCalender(); initCalender();
updateData(); updateData();
@ -225,90 +261,34 @@ public class CommonHomeFragment extends Fragment {
private void setupToolBar() { private void setupToolBar() {
if (isChildMode) { if (isChildMode) {
toolBarManager.setTitle("ホーム"); toolBarManager.setTitle("タスク一覧");
toolBarManager.setSubtitle("子供ビュー");
} else { } else {
toolBarManager.setTitle("ホーム"); 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 タスク名 * @param taskName タスク名
* @return OKボタンが押されたかどうか
*/ */
private void showConfirmDialog(String taskId, String taskName) { private boolean showConfirmDialog(String taskName) {
AtomicBoolean selection = new AtomicBoolean(false);
new MaterialAlertDialogBuilder(requireContext()) new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスクを完了しますか?") .setTitle("タスクを完了しますか?")
.setMessage(taskName + "を完了しますか?") .setMessage(taskName + "を完了しますか?")
.setPositiveButton("はい", (dialog, which) -> { .setPositiveButton("はい", (dialog, which) -> {
dialog.dismiss(); dialog.dismiss();
taskData.recordTaskCompletion(taskId, childId); selection.set(true);
}) })
.setNegativeButton("いいえ", (dialog, which) -> { .setNegativeButton("いいえ", (dialog, which) -> {
dialog.dismiss(); dialog.dismiss();
taskData.recordTaskCompletion(taskId, childId); selection.set(false);
}) })
.show(); .show();
return selection.get();
} }
/** /**
@ -364,11 +344,9 @@ public class CommonHomeFragment extends Fragment {
} }
}).thenAccept(taskItemModel -> { }).thenAccept(taskItemModel -> {
requireActivity().runOnUiThread(() -> { requireActivity().runOnUiThread(() -> {
// taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount()); taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
// taskListItemAdapter.setTaskItemModelList(taskItemModel);
// taskListItemAdapter.notifyItemRangeInserted(0, taskItemModel.size());
taskListItemAdapter.setTaskItemModelList(taskItemModel); taskListItemAdapter.setTaskItemModelList(taskItemModel);
taskListItemAdapter.notifyItemRangeChanged(0, taskItemModel.size()); taskListItemAdapter.notifyItemRangeInserted(0, taskItemModel.size());
}); });
}); });
} }
@ -380,7 +358,7 @@ public class CommonHomeFragment extends Fragment {
private CompletableFuture<Void> updateCalender() { private CompletableFuture<Void> updateCalender() {
return rewardData.getRewardHistoryList().thenAccept(historyModels -> { return rewardData.getRewardHistoryList().thenAccept(historyModels -> {
historyModels.forEach(historyModel -> { historyModels.forEach(historyModel -> {
compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel)); // debug compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel.getTaskName())); // debug
}); });
}); });
} }
@ -390,25 +368,10 @@ public class CommonHomeFragment extends Fragment {
@Override @Override
public void onDayClick(Date date) { // Test public void onDayClick(Date date) { // Test
List<Event> events = compactCalendarView.getEvents(date); 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()) new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスク一覧 (DEBUG)") .setTitle(date.toString())
.setMessage(events.size() + "件のタスクが登録されています") .setMessage(events.toString())
.setView(scrollView) .setPositiveButton("OK", (dialog, which) -> dialog.dismiss())
.setNeutralButton("閉じる", (dialog, which) -> dialog.dismiss())
.show(); .show();
} }
@ -436,35 +399,20 @@ public class CommonHomeFragment extends Fragment {
* データを更新 (updateTaskInfoとupdateCalenderを並列実行) * データを更新 (updateTaskInfoとupdateCalenderを並列実行)
*/ */
private void updateData() { private void updateData() {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(true); swipeRefreshLayout.setRefreshing(true);
});
CompletableFuture.allOf(updateTaskInfo(), updateCalender()).thenRun(() -> { CompletableFuture.allOf(updateTaskInfo(), updateCalender()).thenRun(() -> {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
}); });
});
} }
/** /**
* タスク追加ダイアログを表示 * タスク追加ダイアログを表示
*/ */
private void showAddTaskDialog() { private void showAddTaskDialog() {
View view = getLayoutInflater().inflate(R.layout.common_task_add_dialog_layout, null);
view.setPadding(48, 24, 48, 24);
new MaterialAlertDialogBuilder(requireContext()) new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスクを追加") .setTitle("Placeholder")
.setView(view) .setMessage("Placeholder")
.setPositiveButton("追加", (dialog, which) -> { .setPositiveButton("OK", (dialog, which) -> dialog.dismiss())
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(); .show();
} }
} }

View File

@ -22,7 +22,6 @@ import one.nem.kidshift.feature.common.adapter.SelectShowChildListItemAdapter;
import one.nem.kidshift.utils.FabManager; import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.RecyclerViewAnimUtils; import one.nem.kidshift.utils.RecyclerViewAnimUtils;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory; import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint @AndroidEntryPoint
@ -35,8 +34,6 @@ public class CommonSelectChildFragment extends Fragment {
@Inject @Inject
FabManager fabManager; FabManager fabManager;
@Inject @Inject
ToolBarManager toolBarManager;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils; RecyclerViewAnimUtils recyclerViewAnimUtils;
private KSLogger logger; private KSLogger logger;
@ -62,9 +59,9 @@ public class CommonSelectChildFragment extends Fragment {
childListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); childListRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerViewAnimUtils.setSlideUpAnimation(childListRecyclerView); recyclerViewAnimUtils.setSlideUpAnimation(childListRecyclerView);
adapter = new SelectShowChildListItemAdapter(); adapter = new SelectShowChildListItemAdapter();
adapter.setCallback(childId -> { adapter.setCallback(taskId -> {
// 静的解析エラーが発生するのになぜか実行はできる // 静的解析エラーが発生するのになぜか実行はできる
findNavController(view).navigate(CommonSelectChildFragmentDirections.actionCommonSelectChildFragmentToCommonHomeFragmentParentChild(childId)); findNavController(view).navigate(CommonSelectChildFragmentDirections.actionCommonSelectChildFragmentToCommonHomeFragmentParentChild(taskId));
}); });
CompletableFuture.runAsync(() -> childListRecyclerView.setAdapter(adapter)).thenRun(() -> childData.getChildListDirect().thenAccept(childList -> { CompletableFuture.runAsync(() -> childListRecyclerView.setAdapter(adapter)).thenRun(() -> childData.getChildListDirect().thenAccept(childList -> {
requireActivity().runOnUiThread(() -> { requireActivity().runOnUiThread(() -> {
@ -83,8 +80,6 @@ public class CommonSelectChildFragment extends Fragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
fabManager.hide(); fabManager.hide();
toolBarManager.setTitle("子供選択");
toolBarManager.setSubtitle(null);
} }
} }

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

@ -10,9 +10,6 @@
android:name="one.nem.kidshift.feature.common.CommonHomeFragment" android:name="one.nem.kidshift.feature.common.CommonHomeFragment"
android:label="fragment_common_home" android:label="fragment_common_home"
tools:layout="@layout/fragment_common_home" > tools:layout="@layout/fragment_common_home" >
<argument
android:name="childId"
app:argType="string" />
<argument <argument
android:name="isChildMode" android:name="isChildMode"
app:argType="boolean" app:argType="boolean"

View File

@ -21,10 +21,6 @@
<argument <argument
android:name="childId" android:name="childId"
app:argType="string" /> app:argType="string" />
<argument
android:name="isChildMode"
app:argType="boolean"
android:defaultValue="true" />
</action> </action>
</fragment> </fragment>
</navigation> </navigation>

View File

@ -1,253 +0,0 @@
package one.nem.kidshift.wallet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import one.nem.kidshift.model.HistoryModel;
public class HistoryItemListAdapter extends RecyclerView.Adapter<HistoryItemListAdapter.ViewHolder> {
enum ViewType {
WITH_HEADER(1),
ITEM(0);
private final int value;
ViewType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static class HistoryModelExtended extends HistoryModel {
private boolean isChecked;
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
// add isChecked to the constructor
public HistoryModelExtended() {
super();
isChecked = false;
}
// copy constructor
public HistoryModelExtended(HistoryModel historyModel) {
super();
this.setId(historyModel.getId());
this.setPaid(historyModel.isPaid());
this.setChildId(historyModel.getChildId());
this.setRegisteredAt(historyModel.getRegisteredAt());
this.setTaskId(historyModel.getTaskId());
this.setTaskName(historyModel.getTaskName());
this.setReward(historyModel.getReward());
this.setChecked(false);
}
}
public static class HistoryModelExtendedList {
private List<HistoryModelExtended> list;
public List<HistoryModelExtended> getList() {
return list;
}
public void setList(List<HistoryModelExtended> list) {
this.list = list;
}
// clear all checked items
public void clearChecked() {
for (HistoryModelExtended item : list) {
item.setChecked(false);
}
}
// constructor
public HistoryModelExtendedList() {
list = new ArrayList<>();
}
}
public interface CheckBoxChangedCallback {
void onCheckBoxChanged();
}
public boolean hasChecked() {
for (HistoryModelExtended historyModelExtended : historyDataList.getList()) {
if (historyModelExtended.isChecked()) {
return true;
}
}
return false;
}
private HistoryModelExtendedList historyDataList;
private CheckBoxChangedCallback callback;
private boolean hideCheckBox; // for child mode
public void setHistoryDataList(List<HistoryModel> historyDataList) {
this.historyDataList = new HistoryModelExtendedList();
for (HistoryModel historyModel : historyDataList) {
this.historyDataList.getList().add(new HistoryModelExtended(historyModel));
}
}
public void setCallback(CheckBoxChangedCallback callback) {
this.callback = callback;
}
public void setHideCheckBox(boolean hideCheckBox) {
this.hideCheckBox = hideCheckBox;
}
public List<HistoryModel> getCheckedHistoryDataList() {
List<HistoryModel> checkedHistoryDataList = new ArrayList<>();
for (HistoryModelExtended historyModelExtended : historyDataList.getList()) {
if (historyModelExtended.isChecked()) {
checkedHistoryDataList.add(historyModelExtended);
}
}
return checkedHistoryDataList;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return ViewType.WITH_HEADER.getValue();
} else {
if (isFirstOfMonth(historyDataList.getList().get(position))) {
return ViewType.WITH_HEADER.getValue();
} else {
return ViewType.ITEM.getValue();
}
}
}
@NonNull
@Override
public HistoryItemListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ViewType.WITH_HEADER.getValue()) {
LinearLayout view = new LinearLayout(parent.getContext());
view.setOrientation(LinearLayout.VERTICAL);
view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
view.addView(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_history_month_header, parent, false));
view.addView(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_history_list_item, parent, false));
return new MonthHeaderViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_history_list_item, parent, false);
return new ViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull HistoryItemListAdapter.ViewHolder holder, int position) {
HistoryModelExtended historyData = this.historyDataList.getList().get(position);
if (historyData.isPaid() || hideCheckBox) {
holder.historyItemCheckBox.setVisibility(View.GONE);
} else {
holder.historyItemCheckBox.setVisibility(View.VISIBLE);
}
holder.historyItemNameTextView.setText(historyData.getTaskName());
holder.historyItemRewardTextView.setText(historyData.getReward() + "");
holder.historyItemCheckBox.setChecked(historyData.isChecked());
holder.historyItemCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (holder.historyItemCheckBox.isShown()) {
historyData.setChecked(isChecked);
callback.onCheckBoxChanged();
}
});
if (holder instanceof MonthHeaderViewHolder) {
((MonthHeaderViewHolder) holder).monthHeaderTitle.setText(historyData.getRegisteredAt().getMonth() + 1 + "");
// // DEBUG: 月をまたぐデータがないので
// ((MonthHeaderViewHolder) holder).monthHeaderTitle.setText(historyData.getRegisteredAt().getDate() + "");
((MonthHeaderViewHolder) holder).monthTotalTextView.setText(getMonthTotal(historyData) + "");
((MonthHeaderViewHolder) holder).checkAllButton.setOnClickListener(v -> {
Toast.makeText(v.getContext(), "実装中", Toast.LENGTH_SHORT).show();
});
}
}
private boolean isFirstOfMonth(HistoryModel historyModel) {
// 1個前の要素と比較して月が変わったかどうかを判定する
if (historyDataList.getList().indexOf(historyModel) == 0) {
return true;
} else {
HistoryModel previousHistoryModel = historyDataList.getList().get(historyDataList.getList().indexOf(historyModel) - 1);
// getMonth()はDeprecated TODO: やめる
return historyModel.getRegisteredAt().getMonth() != previousHistoryModel.getRegisteredAt().getMonth();
// DEBUG: 月をまたぐデータがないので
// return historyModel.getRegisteredAt().getDate() != previousHistoryModel.getRegisteredAt().getDate();
}
}
private int getMonthTotal(HistoryModel historyModel) {
int total = historyModel.getReward();
int index = historyDataList.getList().indexOf(historyModel) + 1;
try {
while (!isFirstOfMonth(this.historyDataList.getList().get(index))) {
total += historyModel.getReward();
index++;
}
} catch (IndexOutOfBoundsException e) {
// 1個しかない場合 Workaround
// TODO: 例外をひねり潰すのではなくそもそも発生しないようにするべき
}
return total;
}
@Override
public int getItemCount() {
return historyDataList == null ? 0 : historyDataList.getList().size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView historyItemNameTextView;
TextView historyItemRewardTextView;
CheckBox historyItemCheckBox;
public ViewHolder(@NonNull View itemView) {
super(itemView);
historyItemNameTextView = itemView.findViewById(R.id.historyItemNameTextView);
historyItemRewardTextView = itemView.findViewById(R.id.historyItemRewardTextView);
historyItemCheckBox = itemView.findViewById(R.id.checkBox);
}
}
public static class MonthHeaderViewHolder extends HistoryItemListAdapter.ViewHolder {
// かなり邪道な方法だけどとりあえず取得できるので
TextView monthHeaderTitle;
TextView monthTotalTextView;
ImageButton checkAllButton;
public MonthHeaderViewHolder(@NonNull View itemView) {
super(itemView);
monthHeaderTitle = itemView.findViewById(R.id.monthHeaderTitle);
monthTotalTextView = itemView.findViewById(R.id.monthTotalTextView);
checkAllButton = itemView.findViewById(R.id.checkAllButton);
}
}
}

View File

@ -12,17 +12,9 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.KSActions; import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData; import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.UserSettings; import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.utils.FabManager; import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger; 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.factory.KSLoggerFactory;
import one.nem.kidshift.utils.models.FabEventCallback;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@AndroidEntryPoint @AndroidEntryPoint
public class WalletContentFragment extends Fragment { public class WalletContentFragment extends Fragment {
@ -35,21 +27,14 @@ public class WalletContentFragment extends Fragment {
@Inject @Inject
FabManager fabManager; FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject @Inject
UserSettings userSettings; UserSettings userSettings;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils;
private KSLogger logger; private KSLogger logger;
private String childId; private String childId;
private TextView totalRewardTextView; private TextView totalRewardTextView;
private SwipeRefreshLayout swipeRefreshLayout;
private HistoryItemListAdapter historyItemListAdapter;
public WalletContentFragment() { public WalletContentFragment() {
// Required empty public constructor // Required empty public constructor
@ -92,114 +77,28 @@ public class WalletContentFragment extends Fragment {
totalRewardTextView = view.findViewById(R.id.totalRewardTextView); totalRewardTextView = view.findViewById(R.id.totalRewardTextView);
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(() -> {
// updateTotalReward();
updateItems();
swipeRefreshLayout.setRefreshing(false);
});
RecyclerView historyItemRecyclerView = view.findViewById(R.id.historyItemRecyclerView);
recyclerViewAnimUtils.setSlideUpAnimation(historyItemRecyclerView);
historyItemListAdapter = new HistoryItemListAdapter();
historyItemRecyclerView.setAdapter(historyItemListAdapter);
historyItemRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
historyItemListAdapter.setHideCheckBox(userSettings.getAppCommonSetting().isChildMode());
historyItemListAdapter.setCallback(() -> {
if (historyItemListAdapter.hasChecked()) {
fabManager.show();
initFab();
} else {
fabManager.hide();
}
});
return view; return view;
} }
private void initFab() {
fabManager.setFabEventCallback(new FabEventCallback() {
@Override
public void onClicked() {
historyItemListAdapter.getCheckedHistoryDataList().forEach(historyModel -> {
rewardData.payReward(historyModel.getId()).thenRun(() -> {
logger.debug("Paid reward: " + historyModel.getId());
updateItems();
}).exceptionally(throwable -> {
logger.error("Failed to pay reward: " + throwable.getMessage());
return null;
});
});
updateItems(); // workaround
}
@Override
public void onLongClicked() {
}
});
}
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
updateItems();
}
private void updateItems() {
swipeRefreshLayout.setRefreshing(true);
rewardData.getRewardHistoryList(childId).thenAccept(historyList -> {
historyItemListAdapter.setHistoryDataList(historyList);
// totalRewardTextView.setText(String.valueOf(historyList.stream().mapToInt(HistoryModel::getReward).sum()) + "");
requireActivity().runOnUiThread(() -> {
historyItemListAdapter.notifyDataSetChanged();
totalRewardTextView.setText(String.valueOf(historyList.stream().filter(item -> !item.isPaid()).mapToInt(HistoryModel::getReward).sum()) + "");
});
}).thenRun(() -> {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false);
});
}).exceptionally(throwable -> {
logger.error("Failed to get history list: " + throwable.getMessage());
return null;
});
} }
private void updateTotalReward() { private void updateTotalReward() {
swipeRefreshLayout.setRefreshing(true);
rewardData.getTotalReward(childId).thenAccept(totalReward -> { rewardData.getTotalReward(childId).thenAccept(totalReward -> {
logger.debug("Total reward: " + totalReward); logger.debug("Total reward: " + totalReward);
totalRewardTextView.setText(String.valueOf(totalReward) + ""); totalRewardTextView.setText(String.valueOf(totalReward) + "");
}).thenRun(() -> {
requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false);
});
}).exceptionally(throwable -> { }).exceptionally(throwable -> {
logger.error("Failed to get total reward: " + throwable.getMessage()); logger.error("Failed to get total reward: " + throwable.getMessage());
return null; return null;
}); });
rewardData.getRewardHistoryList(childId).thenAccept(historyList -> { // test
historyItemListAdapter.setHistoryDataList(historyList);
historyItemListAdapter.notifyDataSetChanged();
}).exceptionally(throwable -> {
logger.error("Failed to get history list: " + throwable.getMessage());
return null;
});
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
// updateTotalReward(); updateTotalReward();
// updateItems(); fabManager.hide();
// fabManager.hide();
toolBarManager.setTitle("ウォレット");
toolBarManager.setSubtitle(null);
} }
} }

View File

@ -26,10 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData; import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.RewardData; import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.model.ChildModel; import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.model.callback.ChildModelCallback;
import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger; import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory; import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint @AndroidEntryPoint
@ -45,14 +42,9 @@ public class WalletParentWrapperFragment extends Fragment {
@Inject @Inject
RewardData rewardData; RewardData rewardData;
@Inject
FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
private TabLayout tabLayout; private TabLayout tabLayout;
private ViewPager2 viewPager; private ViewPager2 viewPager;
private TabAdapter tabAdapter;
public WalletParentWrapperFragment() { public WalletParentWrapperFragment() {
// Required empty public constructor // Required empty public constructor
@ -72,48 +64,20 @@ public class WalletParentWrapperFragment extends Fragment {
tabLayout = view.findViewById(R.id.tabLayout); tabLayout = view.findViewById(R.id.tabLayout);
viewPager = view.findViewById(R.id.viewPager); viewPager = view.findViewById(R.id.viewPager);
tabAdapter = new TabAdapter(requireActivity()); TabAdapter tabAdapter = new TabAdapter(requireActivity());
// デバッグ用
List<ChildModel> childList = childData.getChildListDirect().join();
tabAdapter.setChildList(childList);
viewPager.setAdapter(tabAdapter); viewPager.setAdapter(tabAdapter);
setupViewPager();
return view;
}
private void setupViewPager() {
// デバッグ用
childData.getChildList(new ChildModelCallback() {
@Override
public void onUnchanged() {
// TODO: impl
}
@Override
public void onUpdated(List<ChildModel> childModelList) {
// TODO: impl
}
@Override
public void onFailed(String message) {
// TODO: impl
}
}).thenAccept(childModels -> {
// childData.getChildListDirect().thenAccept(childModels -> {
tabAdapter.setChildList(childModels);
requireActivity().runOnUiThread(() -> {
tabAdapter.notifyDataSetChanged();
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
tab.setText(childModels.get(position).getName()); tab.setText(childList.get(position).getName());
}).attach(); }).attach();
});
}); return view;
} }
private static class TabAdapter extends FragmentStateAdapter { private static class TabAdapter extends FragmentStateAdapter {
@ -140,12 +104,4 @@ public class WalletParentWrapperFragment extends Fragment {
return childList == null ? 0 : childList.size(); return childList == null ? 0 : childList.size();
} }
} }
@Override
public void onResume() {
super.onResume();
fabManager.hide();
toolBarManager.setTitle("ウォレット");
toolBarManager.setSubtitle(null);
}
} }

View File

@ -16,12 +16,9 @@
android:id="@+id/frameLayout" android:id="@+id/frameLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/colorSurface" android:layout_marginTop="24dp"
android:backgroundTintMode="add" android:layout_marginBottom="24dp"
android:elevation="16px"
app:layout_constraintBottom_toTopOf="@+id/historyItemRecyclerView" app:layout_constraintBottom_toTopOf="@+id/historyItemRecyclerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<LinearLayout <LinearLayout
@ -35,7 +32,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="今月の支払い総額" android:text="今月の支払い総額"
android:textAppearance="@style/TextAppearance.AppCompat.Display1" android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textColor="@color/colorOnSurface"
android:textSize="20sp" /> android:textSize="20sp" />
<TextView <TextView
@ -44,8 +40,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="0000円" android:text="0000円"
android:textAppearance="@style/TextAppearance.AppCompat.Display2" android:textAppearance="@style/TextAppearance.AppCompat.Display2" />
android:textColor="@color/colorOnSurface" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

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:padding="32px">
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16px"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/historyItemNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/historyItemRewardTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,48 +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:paddingHorizontal="48px"
android:paddingVertical="24px">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/monthHeaderTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ふが月"
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
<TextView
android:id="@+id/monthTotalTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0000000円"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<ImageButton
android:id="@+id/checkAllButton"
style="@style/Widget.AppCompat.ImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="24px"
android:tint="@color/colorOnBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/done_all_24px" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.0.2" agp = "8.3.2"
gson = "2.11.0" gson = "2.11.0"
javafaker = "1.0.2" javafaker = "1.0.2"
junit = "4.13.2" junit = "4.13.2"

View File

@ -9,16 +9,14 @@ public class HistoryModel {
private String childId; private String childId;
private Date registeredAt; private Date registeredAt;
private int reward; private int reward;
private boolean isPaid;
public HistoryModel(String id, String taskId, String taskName, String childId, Date registeredAt, int reward, boolean isPaid) { public HistoryModel(String id, String taskId, String taskName, String childId, Date registeredAt, int reward) {
this.id = id; this.id = id;
this.taskId = taskId; this.taskId = taskId;
this.taskName = taskName; this.taskName = taskName;
this.childId = childId; this.childId = childId;
this.registeredAt = registeredAt; this.registeredAt = registeredAt;
this.reward = reward; this.reward = reward;
this.isPaid = isPaid;
} }
public HistoryModel(String id, String taskId, String childId, Date registeredAt) { // 他モデルとのマージが必要なので public HistoryModel(String id, String taskId, String childId, Date registeredAt) { // 他モデルとのマージが必要なので
@ -78,12 +76,4 @@ public class HistoryModel {
public void setReward(int reward) { public void setReward(int reward) {
this.reward = reward; this.reward = reward;
} }
public boolean isPaid() {
return isPaid;
}
public void setPaid(boolean isPaid) {
this.isPaid = isPaid;
}
} }

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,760L257,760L648,369L591,312L200,703L200,760ZM120,840L120,670L648,143Q660,132 674.5,126Q689,120 705,120Q721,120 736,126Q751,132 762,144L817,200Q829,211 834.5,226Q840,241 840,256Q840,272 834.5,286.5Q829,301 817,313L290,840L120,840ZM760,256L760,256L704,200L704,200L760,256ZM619,341L591,312L591,312L648,369L648,369L619,341Z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,760L373.85,760L373.85,558.46Q373.85,543.1 384.24,532.7Q394.64,522.31 410,522.31L550,522.31Q565.36,522.31 575.76,532.7Q586.15,543.1 586.15,558.46L586.15,760L720,760L720,406.15Q720,403.08 718.65,400.58Q717.31,398.08 715,396.15L487.31,225Q484.23,222.31 480,222.31Q475.77,222.31 472.69,225L245,396.15Q242.69,398.08 241.35,400.58Q240,403.08 240,406.15L240,760ZM180,760L180,406.15Q180,388.98 187.68,373.62Q195.37,358.25 208.92,348.31L436.62,176.77Q455.57,162.31 479.94,162.31Q504.31,162.31 523.38,176.77L751.08,348.31Q764.63,358.25 772.32,373.62Q780,388.98 780,406.15L780,760Q780,784.54 762.27,802.27Q744.54,820 720,820L562.31,820Q546.94,820 536.55,809.6Q526.15,799.21 526.15,783.84L526.15,582.31Q526.15,582.31 526.15,582.31Q526.15,582.31 526.15,582.31L433.85,582.31Q433.85,582.31 433.85,582.31Q433.85,582.31 433.85,582.31L433.85,783.84Q433.85,799.21 423.45,809.6Q413.06,820 397.69,820L240,820Q215.46,820 197.73,802.27Q180,784.54 180,760ZM480,490.77L480,490.77L480,490.77Q480,490.77 480,490.77Q480,490.77 480,490.77L480,490.77L480,490.77L480,490.77L480,490.77Q480,490.77 480,490.77Q480,490.77 480,490.77L480,490.77Q480,490.77 480,490.77Q480,490.77 480,490.77L480,490.77Z"/>
</vector>