Compare commits

...

82 Commits

Author SHA1 Message Date
4dab0f33f0 Merge pull request 'ライセンス追加' (#172) from add_license into main
Reviewed-on: #172
2024-07-25 20:24:58 +00:00
d055c5fa14 ライセンス追加 2024-07-26 05:24:13 +09:00
e712a84564 Merge pull request 'update readme' (#171) from doc into main
Reviewed-on: #171
2024-07-24 07:30:04 +00:00
81dfdc1fe2
update readme 2024-07-24 16:29:50 +09:00
ad0ad3685b Merge pull request 'refactor/2' (#170) from refactor/2 into main
Reviewed-on: #170
2024-07-24 07:27:18 +00:00
f192876b4c
子供モード時チェックボックスが表示されないように 2024-07-24 14:47:43 +09:00
764beff222
アイコンリソース追加 2024-07-23 14:39:28 +09:00
21df151e65
RecyclerViewAnimation追加 2024-07-23 14:10:26 +09:00
b3bb61e43b
月修正 2024-07-23 14:10:17 +09:00
8da7b26d72
区切りを月に戻した 2024-07-23 13:56:49 +09:00
aece6909ad Merge pull request 'refactor/refactor1' (#169) from refactor/refactor1 into main
Reviewed-on: #169
2024-07-23 04:45:48 +00:00
bba2751704
判定ミス修正 2024-07-23 13:44:47 +09:00
7794d664ba
カレンダーダイアログ修正 2024-07-23 13:43:52 +09:00
bef4d02dae Merge pull request 'feature/improve-history' (#168) from feature/improve-history into main
Reviewed-on: #168
2024-07-23 04:32:58 +00:00
3000fe9989
自動更新するように 2024-07-23 13:31:18 +09:00
ecafb0c13a
UI微修正 2024-07-23 13:30:39 +09:00
c9e1706374
UI微修正 2024-07-23 13:29:45 +09:00
bad3b72c91
実行後更新するように 2024-07-23 13:20:57 +09:00
53614cf033
キャッシュありに戻した 2024-07-23 13:16:39 +09:00
2d9c1a10c9
WIP 2024-07-23 13:15:52 +09:00
fbfb02ad09
起動時処理修正 2024-07-23 13:12:21 +09:00
6213acc2b0
取得処理修正 2024-07-23 13:08:59 +09:00
7d01ebc018
Paidを集計に含めないように 2024-07-23 13:05:18 +09:00
3d222cafe0
レイアウト修正 2024-07-23 13:05:10 +09:00
c6ee2dc75e
チェックボックスを全部隠すフラグを追加 2024-07-23 13:02:29 +09:00
6a4ad4de05
支払い済みアイテムも取得対象に 2024-07-23 12:56:23 +09:00
942e7b561b
支払い済みの場合はチェックボックスを非表示に 2024-07-23 12:54:24 +09:00
03337612a7
テスト実装 2024-07-23 12:50:18 +09:00
0421d84bd6
PayReward実装 2024-07-23 12:49:49 +09:00
fc7e709cf6
API追加 wip 2024-07-23 12:45:15 +09:00
1ae8335a4a
Revert "Rename"
This reverts commit cbf7f843b2.
2024-07-23 12:44:08 +09:00
cbf7f843b2
Rename 2024-07-23 12:33:42 +09:00
870b8be7f6
リフレッシュ処理改善 2024-07-23 12:28:42 +09:00
f932b935b9
WIP 2024-07-23 12:28:20 +09:00
ede51b124f
WIP 2024-07-23 12:22:22 +09:00
4ba2677a15
WIP 2024-07-23 12:19:28 +09:00
f2c4e43b98
Setter 2024-07-23 12:15:33 +09:00
8129b83e16
いろいろ 2024-07-23 12:15:04 +09:00
62d298ebfd
とりあえず廃止 2024-07-23 12:11:44 +09:00
4916c05b08
効率化, リファクタ, 修正 2024-07-23 12:00:10 +09:00
161d9d7602
ID修正 2024-07-23 11:50:07 +09:00
a4986cffbc
全部✓ボタン挙動 WIP 2024-07-23 11:49:54 +09:00
f4b116cd27
例外workaround 2024-07-23 11:47:15 +09:00
86093a1e13
総額表示実装 WIP 2024-07-23 11:42:16 +09:00
7ecd46fb98
総額表示実装 WIP 2024-07-23 11:41:58 +09:00
7f8c9d74fa
レイアウト修正 WIP 2024-07-23 11:39:08 +09:00
98cacfd561
レイアウト修正 WIP 2024-07-23 11:37:00 +09:00
9a8d9ef631
TextView書き換えテスト 2024-07-23 11:17:28 +09:00
dc83d541e2
test wip 2024-07-23 11:16:28 +09:00
3a6fc8cdd7
ID修正 2024-07-23 11:16:21 +09:00
73e2c0f1d9
ViewHolder追加 2024-07-23 11:13:49 +09:00
696486623e
リファクタ 2024-07-23 11:09:49 +09:00
70db920060
方向修正 2024-07-23 11:08:54 +09:00
7ea5e143d6
暫定ヘッダーを挿入するように 2024-07-23 11:07:01 +09:00
8ea083cdf4
レイアウト微修正 2024-07-23 11:06:27 +09:00
9173db1f88
レイアウトパラメータ追加 2024-07-23 11:05:40 +09:00
8f77c73439
デバッグ用に日付を用いるように 2024-07-23 11:04:55 +09:00
20a24b4ea3
暫定実装 2024-07-23 11:03:37 +09:00
fc0091b24a
WIP debug 2024-07-23 11:00:13 +09:00
06c392af2e
テスト用レイアウト作成 2024-07-23 10:48:15 +09:00
e2a75ba6d7
Checkedなアイテムを一括取得できるように 2024-07-23 10:43:01 +09:00
fb3b15450d
回避 2024-07-23 10:42:29 +09:00
36ed1a0ab9
確実に元データを書き換えるように? 2024-07-23 10:35:55 +09:00
7c2496c373
コンストラクタで初期化するように 2024-07-23 10:34:48 +09:00
8095095ce8
コンストラクタで処理するように 2024-07-23 10:33:49 +09:00
6496e218af
呼び出し変更 2024-07-23 10:33:39 +09:00
9f1a9b64a9
チェックボックス挙動実装 WIP 2024-07-23 10:17:18 +09:00
dc21fee2cd
レイアウト修正 2024-07-23 10:15:28 +09:00
3959cbba5f
エラー修正 2024-07-23 10:12:48 +09:00
69689e7247
Streamにした 2024-07-23 10:12:19 +09:00
f73bfeeb03
WIP 2024-07-23 10:12:10 +09:00
a6612c5ea9
test 2024-07-22 12:23:42 +09:00
43d00c90c3
リファクタ 2024-07-22 12:18:45 +09:00
5d5804a1f6
iroiro 2024-07-22 12:01:16 +09:00
b08b1f54e4
View 2024-07-22 11:59:30 +09:00
aa9b997117
代入追加 2024-07-22 11:42:10 +09:00
805cb93d59
static 2024-07-22 11:41:58 +09:00
49e8acbe2f
Adapterひな形実装 2024-07-22 11:40:27 +09:00
3da941e0ae
仮レイアウト作成 2024-07-22 11:37:48 +09:00
c79f9e5469
isPaidを追加 2024-07-22 11:33:05 +09:00
a0cf1f3ab3
isPaidを追加 2024-07-22 11:32:25 +09:00
21ec30ef19
isPaidを追加 2024-07-22 11:31:32 +09:00
31 changed files with 641 additions and 54 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
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,8 +1,38 @@
# WIP
## メモ
~~## メモ
- リリース前(=提出前)には`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.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,30 +1,15 @@
<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="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>
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">
<path
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>
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>
</vector>

View File

@ -0,0 +1,5 @@
<?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

@ -0,0 +1,5 @@
<?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: 1.4 KiB

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

View File

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

View File

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

View File

@ -10,11 +10,13 @@ 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 {
@ -23,14 +25,15 @@ public class RewardDataImpl implements RewardData {
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) {
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");
}
@ -53,4 +56,30 @@ public class RewardDataImpl implements RewardData {
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

@ -185,6 +185,9 @@ public interface KidShiftApiService {
@GET("/task/history/{childId}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<HistoryListResponse> getHistory(@Path("childId") String childId);
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

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

View File

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

View File

@ -24,6 +24,8 @@ 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;
@ -46,6 +48,7 @@ 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;
@ -377,7 +380,7 @@ public class CommonHomeFragment extends Fragment {
private CompletableFuture<Void> updateCalender() {
return rewardData.getRewardHistoryList().thenAccept(historyModels -> {
historyModels.forEach(historyModel -> {
compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel.getTaskName())); // debug
compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel)); // debug
});
});
}
@ -387,10 +390,25 @@ public class CommonHomeFragment extends Fragment {
@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(date.toString())
.setMessage(events.toString())
.setPositiveButton("OK", (dialog, which) -> dialog.dismiss())
.setTitle("タスク一覧 (DEBUG)")
.setMessage(events.size() + "件のタスクが登録されています")
.setView(scrollView)
.setNeutralButton("閉じる", (dialog, which) -> dialog.dismiss())
.show();
}

View File

@ -0,0 +1,253 @@
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,10 +12,16 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.model.HistoryModel;
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;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@AndroidEntryPoint
@ -34,6 +40,8 @@ public class WalletContentFragment extends Fragment {
@Inject
UserSettings userSettings;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils;
private KSLogger logger;
private String childId;
@ -41,6 +49,8 @@ public class WalletContentFragment extends Fragment {
private TextView totalRewardTextView;
private SwipeRefreshLayout swipeRefreshLayout;
private HistoryItemListAdapter historyItemListAdapter;
public WalletContentFragment() {
// Required empty public constructor
}
@ -85,16 +95,79 @@ public class WalletContentFragment extends Fragment {
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(() -> {
updateTotalReward();
// 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;
}
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
public void onViewCreated(View view, Bundle 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() {
@ -110,13 +183,22 @@ public class WalletContentFragment extends Fragment {
logger.error("Failed to get total reward: " + throwable.getMessage());
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
public void onResume() {
super.onResume();
updateTotalReward();
fabManager.hide();
// updateTotalReward();
// updateItems();
// fabManager.hide();
toolBarManager.setTitle("ウォレット");
toolBarManager.setSubtitle(null);
}

View File

@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.RewardData;
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.ToolBarManager;
@ -51,6 +52,7 @@ public class WalletParentWrapperFragment extends Fragment {
private TabLayout tabLayout;
private ViewPager2 viewPager;
private TabAdapter tabAdapter;
public WalletParentWrapperFragment() {
// Required empty public constructor
@ -70,22 +72,50 @@ public class WalletParentWrapperFragment extends Fragment {
tabLayout = view.findViewById(R.id.tabLayout);
viewPager = view.findViewById(R.id.viewPager);
TabAdapter tabAdapter = new TabAdapter(requireActivity());
// デバッグ用
List<ChildModel> childList = childData.getChildListDirect().join();
tabAdapter.setChildList(childList);
tabAdapter = new TabAdapter(requireActivity());
viewPager.setAdapter(tabAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
tab.setText(childList.get(position).getName());
}).attach();
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) -> {
tab.setText(childModels.get(position).getName());
}).attach();
});
});
}
private static class TabAdapter extends FragmentStateAdapter {
private List<ChildModel> childList;

View File

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

View File

@ -0,0 +1,45 @@
<?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

@ -0,0 +1,48 @@
<?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

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