Compare commits

...

195 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
05e8d15a20 Merge pull request 'fix/fix_child_uinfo' (#167) from fix/fix_child_uinfo into main
Reviewed-on: #167
2024-07-12 02:57:10 +00:00
d33a454ecc
子供モード時編集ボタンをいったん非表示に 2024-07-12 11:56:45 +09:00
aace66143f
文言修正 2024-07-12 11:54:59 +09:00
e90fd8a759
実装改善 2024-07-12 11:54:09 +09:00
9b0b710c73
getChild実装 WIP 2024-07-12 11:54:03 +09:00
16f2ed9c20 Merge pull request 'fix/fix_icon' (#166) from fix/fix_icon into main
Reviewed-on: #166
2024-07-12 02:49:24 +00:00
7039271e45
アイコン変更 2024-07-12 11:48:32 +09:00
81ebc36c98
アイコンリソース追加 2024-07-12 11:47:32 +09:00
a37dbc9445 Merge pull request 'ウォレット画面のスワイプリフレッシュを修正' (#165) from fix/fix_pull_to_refresh into main
Reviewed-on: #165
2024-07-12 02:19:22 +00:00
4293b477f5 Merge pull request '子供がタスクを完了できない不具合を修正' (#164) from fix/fix_child_complete into main
Reviewed-on: #164
2024-07-12 02:18:57 +00:00
a5dcde0d54
スワイプしてリフレッシュを修正 2024-07-12 11:18:19 +09:00
6160eba7ac
修正 2024-07-12 11:13:29 +09:00
48949d3c60 Merge pull request 'AGPバージョンをダウングレード' (#163) from fix/support_flamingo into main
Reviewed-on: #163
2024-07-09 10:34:16 +00:00
d9cb4ecfb2
AGPバージョンをダウングレード 2024-07-09 19:32:49 +09:00
00ccb3a5f0 Merge pull request 'improve/all' (#162) from improve/all into main
Reviewed-on: #162
2024-07-09 09:35:20 +00:00
eb2e2e5b6d
ヘッダーレイアウト修正 2024-07-09 16:25:43 +09:00
94f47982f7
rename 2024-07-09 16:22:23 +09:00
cc5560745f
cache取得追加 2024-07-09 16:18:39 +09:00
358116811f
暫定対応 2024-07-09 16:12:17 +09:00
73bcefa99f
parentRenameRequest 2024-07-09 16:05:05 +09:00
c846659e4b
Singleton化 2024-07-09 16:00:43 +09:00
10374d2486
不要な引数を削除 2024-07-09 15:58:18 +09:00
2963ffec7e
実装 WIP 2024-07-09 15:58:07 +09:00
e714b26921
getParentDirect追加 2024-07-09 15:43:14 +09:00
57b22b8ec4
レイアウト調整 2024-07-09 15:39:52 +09:00
555e0d3862
アイコンリソース追加 2024-07-09 15:38:18 +09:00
c183d43643
レイアウト仮作成 2024-07-09 15:37:11 +09:00
793ff62b41
アイコン変更 2024-07-09 15:29:59 +09:00
bb2298598d
メニューリソース追加 2024-07-09 15:28:51 +09:00
00febce148
toolBar, Fabの明示追加 2024-07-09 15:09:42 +09:00
a033a3888e
引数指定 2024-07-09 15:01:35 +09:00
6769397484
挙動修正 2024-07-09 15:01:26 +09:00
c43ec08318
挙動修正 2024-07-09 14:21:57 +09:00
10ebcae612
マージン削除 2024-07-09 14:21:50 +09:00
38e9277e9b
addTaskDialog暫定再実装 2024-07-09 14:18:46 +09:00
b9fed3ab71
2024-07-09 13:53:12 +09:00
e01a28b1c4
テキスト変更 2024-07-09 13:52:37 +09:00
97ff8da8d8 Merge pull request 'improve/all' (#161) from improve/all into main
Reviewed-on: #161
2024-07-09 04:43:15 +00:00
39f4316345
fix 2024-07-09 13:42:46 +09:00
42a972855d
fix 2024-07-09 13:09:29 +09:00
15426d358c
更新に対応 2024-07-09 13:08:38 +09:00
c01f3012dd
アニメーション時間調整 2024-07-09 13:06:36 +09:00
4d2f177a88
一時的に消していた処理を復元 2024-07-09 13:06:29 +09:00
364f580fbc
WIP 2024-07-09 12:59:38 +09:00
97e6bc072e
アニメーション実装 WIP 2024-07-09 12:44:52 +09:00
8495e8021e
アニメーション修正 2024-07-09 12:44:44 +09:00
f052710cd8
アニメーション追加 2024-07-09 12:37:51 +09:00
4fa39cb49b
MenuHostで代替した関数を削除 2024-07-09 12:36:00 +09:00
f82fcf090f
アクション実装 WIP 2024-07-09 12:35:19 +09:00
434e52bf4a
Workaround 2024-07-09 12:29:37 +09:00
c8c3215c3f
WIP 2024-07-09 12:07:19 +09:00
34e5085b63
改善 2024-07-09 12:06:54 +09:00
4974bfced6
アニメーションでラグをごまかせたので遅延を削除 2024-07-09 11:49:16 +09:00
fbf49bbe43
実験実装削除 2024-07-09 11:48:31 +09:00
dd32a82b7b
toolbarManagerTest 2024-07-09 11:47:33 +09:00
f1c181a755
setToolbar追加 2024-07-09 11:46:10 +09:00
0993e46d48
ToolbarManager実装 WIP 2024-07-09 11:45:32 +09:00
5b01c048f9
アニメーション付与 WIP 2024-07-09 11:41:53 +09:00
a63deb01ac
ユーティリティでアニメーションを付与するように 2024-07-09 11:30:43 +09:00
6fe88d9ebf
戻り値を廃止 2024-07-09 11:29:50 +09:00
160be606ae
WIP 2024-07-09 11:27:58 +09:00
23993285af
実装削除 2024-07-09 11:25:32 +09:00
47134fcde6
depend移動 2024-07-09 11:25:27 +09:00
dcec3c2e9e
ユーティリティ作成 WIP 2024-07-09 11:24:37 +09:00
6fad20c34a
WIP 2024-07-09 11:24:29 +09:00
bdcd30c6f8
アニメーションを適用するために実装を修正 2024-07-09 11:19:43 +09:00
e6c8a85c95
Animatorsを依存に追加 2024-07-09 11:10:14 +09:00
61eead89c6
テスト用アニメーション追加 2024-07-09 11:03:27 +09:00
e70bfb17cd
遅延で回避 2024-07-09 10:52:56 +09:00
dde0edfad7
menuアイテム作成 2024-07-09 10:49:35 +09:00
1b1e57fb49
アイコンリソース追加 2024-07-09 10:49:03 +09:00
07c7f1c6fb
menuリソース作成 2024-07-09 10:47:47 +09:00
cc13bdd37f
コメント 2024-07-09 10:46:49 +09:00
8761e5ef81
toolBarの参照を外から取れるように 2024-07-09 10:46:21 +09:00
04fd81a547
toolBarをインスタンス変数に 2024-07-09 10:45:39 +09:00
7142a5d30f
Inject整理, リネーム 2024-07-09 10:45:07 +09:00
a4d838c781
初回もカレンダーのタイトルを設定するように 2024-07-09 10:42:00 +09:00
dc9b0a080f
ボタン, タイトルを設定 2024-07-09 10:41:17 +09:00
91b07b4aae
レイアウト WIP 2024-07-09 10:36:35 +09:00
cfdc160cad
レイアウト WIP 2024-07-09 10:33:59 +09:00
43cd603fcc
WIP 2024-07-09 10:28:50 +09:00
9b8fa59f9a
アイコンリソース追加 2024-07-09 10:28:40 +09:00
5f029a3a4f
色修正 2024-07-09 10:19:04 +09:00
34497196d5
カレンダーデザイン変更, WIP 2024-07-09 10:13:10 +09:00
43a20fe409
明示的にFabを隠すように 2024-07-09 10:09:02 +09:00
e4c36e6971 Merge pull request 'feature/calendar' (#160) from feature/calendar into main
Reviewed-on: #160
2024-07-09 01:04:56 +00:00
9efe6e12d4
仮ダイアログ設置 2024-07-09 10:02:27 +09:00
bb64622cc3
暫定表示処理実装 2024-07-09 10:00:05 +09:00
6c7f00249a
暫定取得メソッド実装 2024-07-09 09:56:53 +09:00
c0f371a892
インタフェース追加 2024-07-09 09:53:21 +09:00
731c2b8033 Merge pull request 'feature/parent_child' (#159) from feature/parent_child into main
Reviewed-on: #159
2024-07-08 20:37:30 +00:00
rca
94807508ee 子供を選んで飛べるように 2024-07-09 05:36:07 +09:00
rca
c1244accca Add 2024-07-09 05:32:50 +09:00
rca
4b163f6202 順序修正 2024-07-09 05:32:40 +09:00
rca
075bae4d2d SafeArgs追加 WIP 2024-07-09 05:32:23 +09:00
rca
9f930438ff id訂正忘れ修正, リネーム 2024-07-09 05:19:17 +09:00
rca
0df125ba2f レイアウト指定ミス修正 2024-07-09 05:18:18 +09:00
rca
e252f557d0 UIスレッドで実行するように 2024-07-09 05:17:26 +09:00
rca
9f07171543 セット忘れ修正 2024-07-09 05:16:30 +09:00
rca
722a66e1f0 loggerのinitを一番最初に行うように 2024-07-09 05:15:11 +09:00
rca
fa1bdfce4b 不要なログを削除 2024-07-09 05:14:45 +09:00
rca
6969e841b9 ボタンリスナー追加 2024-07-09 05:11:57 +09:00
rca
70f530a8d3 arg追加 2024-07-09 05:11:08 +09:00
rca
1960a83185 navigation修正 2024-07-09 05:09:48 +09:00
rca
a18a9eaf1c 暫定 2024-07-09 05:08:29 +09:00
rca
e8f4a5d66c レイアウト移植, 微修正 2024-07-09 05:06:36 +09:00
rca
ab8d309328 Adapter移植 2024-07-09 05:06:26 +09:00
rca
b025fc3abb id修正 2024-07-09 05:02:17 +09:00
rca
24c40d05e3 暫定レイアウト作成 2024-07-09 05:01:50 +09:00
rca
f0182b25c2 init 2024-07-09 04:58:49 +09:00
rca
6433a0d09a cleanup 2024-07-09 04:57:43 +09:00
rca
b416e93ab3 SelectChildFragment WIP 2024-07-09 04:57:22 +09:00
b433c8e7ed Merge pull request 'feature/child_mode_2' (#158) from feature/child_mode_2 into main
Reviewed-on: #158
2024-07-08 19:55:26 +00:00
71 changed files with 1793 additions and 142 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 # 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.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -4,6 +4,8 @@ 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;
@ -20,10 +22,10 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
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;
@ -32,32 +34,42 @@ 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;
import one.nem.kidshift.utils.ToolBarManager;
import one.nem.kidshift.utils.factory.KSLoggerFactory; import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint @AndroidEntryPoint
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
@Inject @Inject
KSLoggerFactory loggerFactory; KSLoggerFactory ksLoggerFactory;
@Inject @Inject
FabManager fabManager; FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject @Inject
FeatureFlag featureFlag; FeatureFlag featureFlag;
@Inject
UserSettings userSettings;
@Inject
ParentData parentData;
@Inject
ChildData childData;
private KSLogger logger; private KSLogger logger;
private FloatingActionButton fab; private FloatingActionButton fab;
private Toolbar toolbar;
@Inject
UserSettings userSettings;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -70,6 +82,8 @@ public class MainActivity extends AppCompatActivity {
return insets; return insets;
}); });
logger = ksLoggerFactory.create("MainActivity");
// Check logged in // Check logged in
if (userSettings.getAppCommonSetting().isLoggedIn()) { if (userSettings.getAppCommonSetting().isLoggedIn()) {
logger.info("User is logged in!"); logger.info("User is logged in!");
@ -80,8 +94,9 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent); startActivity(intent);
} }
Toolbar toolbar = findViewById(R.id.toolbar); toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
toolBarManager.setToolbar(toolbar);
DrawerLayout drawerLayout = findViewById(R.id.drawerLayout); DrawerLayout drawerLayout = findViewById(R.id.drawerLayout);
@ -98,6 +113,9 @@ 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());
} }
@ -111,10 +129,6 @@ public class MainActivity extends AppCompatActivity {
drawerLayout.addDrawerListener(actionBarDrawerToggle); drawerLayout.addDrawerListener(actionBarDrawerToggle);
actionBarDrawerToggle.syncState(); actionBarDrawerToggle.syncState();
logger = loggerFactory.create("MainActivity");
logger.info("MainActivity started!");
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav); BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav);
// Init navigation // Init navigation
@ -159,6 +173,70 @@ 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);
@ -208,5 +286,8 @@ public class MainActivity extends AppCompatActivity {
return divider; return divider;
} }
public Toolbar getToolbar() { // TODO: toolbarのインスタンス自体を取得するのではなくfabのように操作できるようにする
return toolbar;
}
} }

View File

@ -1,30 +1,15 @@
<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="108" android:viewportWidth="24"
android:viewportHeight="108"> android:viewportHeight="24"
<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"> android:tint="#333333">
<aapt:attr name="android:fillColor"> <group android:scaleX="0.435"
<gradient android:scaleY="0.435"
android:endX="85.84757" android:translateX="6.78"
android:endY="92.4963" android:translateY="6.78">
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="#FFFFFF" android:fillColor="@android:color/white"
android:fillType="nonZero" 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: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" </group>
android:strokeWidth="1" </vector>
android:strokeColor="#00000000" />
</vector>

View File

@ -20,13 +20,14 @@
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/colorPrimary" android:background="?attr/colorSurface"
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,19 +2,15 @@
<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:text="Header Title" android:layout_marginTop="48dp"
android:text="KidShift"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="20sp" /> android:textSize="34sp" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,52 @@
<?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/pending_24px" android:icon="@drawable/home_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/child_care_24px" android:icon="@drawable/home_24px"
android:title="ホーム" /> android:title="ホーム" />
<item <item

View File

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

@ -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

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

View File

@ -14,10 +14,22 @@ 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 親ユーザー情報
*/ */
void updateParent(ParentModel parent); CompletableFuture<Void> updateParent(ParentModel parent);
} }

View File

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

View File

@ -36,7 +36,21 @@ public class ChildDataImpl implements ChildData {
@Override @Override
public CompletableFuture<ChildModel> getChild(String childId) { public CompletableFuture<ChildModel> getChild(String childId) {
return null; return CompletableFuture.supplyAsync(() -> {
Call<ChildResponse> call = kidShiftApiService.getChild(childId);
try {
Response<ChildResponse> response = call.execute();
if (response.isSuccessful()) {
assert response.body() != null;
logger.info("子供取得成功(childId: " + response.body().getId() + ")");
return ChildModelConverter.childResponseToChildModel(response.body());
} else {
throw new RuntimeException("HTTP Status: " + response.code());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} }
@Override @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); Call<HistoryListResponse> call = kidShiftApiService.getHistory(childId, true); // TODO: containPaidを引数に
try { try {
Response<HistoryListResponse> response = call.execute(); Response<HistoryListResponse> response = call.execute();
if (!response.isSuccessful()) { if (!response.isSuccessful()) {

View File

@ -8,21 +8,26 @@ 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;
@ -46,8 +51,27 @@ public class ParentDataImpl implements ParentData {
} }
@Override @Override
public void updateParent(ParentModel parent) { public CompletableFuture<ParentModel> getParentDirect() {
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

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

View File

@ -4,11 +4,12 @@ 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(FragmentComponent.class) @InstallIn(SingletonComponent.class)
public abstract class ParentDataModule { public abstract class ParentDataModule {
@Binds @Binds

View File

@ -8,6 +8,7 @@ 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;
@ -45,6 +46,15 @@ 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
@ -127,6 +137,15 @@ 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
@ -166,6 +185,9 @@ 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); 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.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;
} }
@ -28,6 +29,7 @@ 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.getDisplayName(), parentInfoResponse.getEmail()); return new ParentModel(parentInfoResponse.getId(), parentInfoResponse.getDisplay_name(), 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 displayName; private String display_name;
/** /**
* コンストラクタ (全プロパティ) * コンストラクタ (全プロパティ)
* @param id 親ID * @param id 親ID
* @param email メールアドレス * @param email メールアドレス
* @param displayName 表示名 * @param display_name 表示名
*/ */
public ParentInfoResponse(String id, String email, String displayName) { public ParentInfoResponse(String id, String email, String display_name) {
this.id = id; this.id = id;
this.email = email; this.email = email;
this.displayName = displayName; this.display_name = display_name;
} }
public String getId() { public String getId() {
@ -26,8 +26,8 @@ public class ParentInfoResponse {
return email; return email;
} }
public String getDisplayName() { public String getDisplay_name() {
return displayName; return display_name;
} }
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 setDisplayName(String displayName) { public void setDisplay_name(String display_name) {
this.displayName = displayName; this.display_name = display_name;
} }
} }

View File

@ -0,0 +1,21 @@
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,12 +7,14 @@ 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) { public HistoryBaseItem(String id, String taskId, String childId, Date registeredAt, boolean isPaid) {
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() {
@ -49,4 +51,12 @@ 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

@ -1,6 +1,7 @@
plugins { plugins {
alias(libs.plugins.androidLibrary) alias(libs.plugins.androidLibrary)
id 'com.google.dagger.hilt.android' id 'com.google.dagger.hilt.android'
id 'androidx.navigation.safeargs'
} }
android { android {

View File

@ -1,21 +1,41 @@
package one.nem.kidshift.feature.common; package one.nem.kidshift.feature.common;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.view.MenuHost;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment; import androidx.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;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.sundeepk.compactcalendarview.CompactCalendarView; import com.github.sundeepk.compactcalendarview.CompactCalendarView;
import com.github.sundeepk.compactcalendarview.domain.Event;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -23,13 +43,18 @@ 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.ChildData;
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;
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 one.nem.kidshift.utils.models.FabEventCallback;
@ -47,14 +72,27 @@ public class CommonHomeFragment extends Fragment {
ChildData childData; ChildData childData;
@Inject @Inject
FabManager fabManager; FabManager fabManager;
@Inject
ToolBarManager toolBarManager;
@Inject
RewardData rewardData;
@Inject
RecyclerViewAnimUtils recyclerViewAnimUtils;
@Inject
UserSettings userSettings;
private boolean isChildMode; private boolean isChildMode;
private String childId; private String childId;
private KSLogger logger; private KSLogger logger;
CompactCalendarView compactCalendarView; CompactCalendarView compactCalendarView;
View calendarContainer;
SwipeRefreshLayout swipeRefreshLayout; SwipeRefreshLayout swipeRefreshLayout;
TaskListItemAdapter taskListItemAdapter; TaskListItemAdapter taskListItemAdapter;
TextView calendarTitleTextView;
ImageButton calendarPrevButton;
ImageButton calendarNextButton;
public CommonHomeFragment() { public CommonHomeFragment() {
// Required empty public constructor // Required empty public constructor
@ -66,9 +104,7 @@ 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;
} }
@ -76,16 +112,30 @@ 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 (isChildMode) { if (userSettings.getAppCommonSetting().isChildMode()) {
logger.info("Child mode, childId: " + childId); logger.debug("子供モードで起動(子供ログイン)");
isChildMode = true;
childId = userSettings.getAppCommonSetting().getChildId();
logger.debug("childId: " + childId);
} else { } else {
logger.info("Parent mode"); if (getArguments() != null) {
isChildMode = getArguments().getBoolean(ARG_IS_CHILD_MODE) && getArguments().getBoolean(ARG_IS_CHILD_MODE);
childId = getArguments().getString(ARG_CHILD_ID) != null ? getArguments().getString(ARG_CHILD_ID) : "";
}
if (childId != null && !childId.isEmpty()) {
isChildMode = true;
}
if (isChildMode) {
logger.debug("子供モードで起動");
logger.debug("childId: " + childId);
} else {
logger.debug("保護者モードで起動");
}
} }
} }
@ -99,9 +149,7 @@ public class CommonHomeFragment extends Fragment {
taskListItemAdapter = new TaskListItemAdapter(); taskListItemAdapter = new TaskListItemAdapter();
taskListItemAdapter.setCallback((taskId, taskName) -> { taskListItemAdapter.setCallback((taskId, taskName) -> {
if (isChildMode) { if (isChildMode) {
if (showConfirmDialog(taskName)) { showConfirmDialog(taskId, taskName);
taskData.recordTaskCompletion(taskId, childId);
}
} else { } else {
showChildSelectDialog(taskId, taskName); showChildSelectDialog(taskId, taskName);
} }
@ -110,22 +158,44 @@ 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);
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(this::updateData); swipeRefreshLayout.setOnRefreshListener(this::updateData);
calendarTitleTextView = view.findViewById(R.id.calendarTitleTextView);
calendarPrevButton = view.findViewById(R.id.calendarPrevButton);
calendarNextButton = view.findViewById(R.id.calendarNextButton);
calendarContainer = view.findViewById(R.id.calendarContainer);
initCalender();
updateData();
return view; return view;
} }
private void recyclerViewRefresh() {
requireActivity().runOnUiThread(() -> {
taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
taskListItemAdapter.notifyItemRangeInserted(0, taskListItemAdapter.getItemCount());
});
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
updateData();
if (isChildMode) { if (isChildMode) {
setupFabChild(); setupFabChild();
} else { } else {
setupFabParent(); setupFabParent();
} }
setupToolBar();
} }
/** /**
@ -153,27 +223,92 @@ public class CommonHomeFragment extends Fragment {
fabManager.hide(); fabManager.hide();
} }
private void setupToolBar() {
if (isChildMode) {
toolBarManager.setTitle("ホーム");
toolBarManager.setSubtitle("子供ビュー");
} else {
toolBarManager.setTitle("ホーム");
toolBarManager.setSubtitle("保護者ビュー");
}
MenuHost menuHost = requireActivity();
menuHost.invalidateMenu();
menuHost.addMenuProvider(new MenuProvider() {
@Override
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
logger.debug("onCreateMenu, インフレート");
menu.clear();
menuInflater.inflate(R.menu.common_home_toolbar_menu, menu);
}
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
if (menuItem.getItemId() == R.id.toggle_calendar) {
if (calendarContainer.getVisibility() == View.VISIBLE) {
Animation slideUp = AnimationUtils.loadAnimation(getContext(), one.nem.kidshift.shared.R.anim.slide_up);
calendarContainer.startAnimation(slideUp);
slideUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
recyclerViewRefresh();
}
@Override
public void onAnimationEnd(Animation animation) {
calendarContainer.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
} else {
Animation slideDown = AnimationUtils.loadAnimation(getContext(), one.nem.kidshift.shared.R.anim.slide_down);
calendarContainer.startAnimation(slideDown);
slideDown.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
calendarContainer.setVisibility(View.VISIBLE);
recyclerViewRefresh();
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return true;
}
return false;
}
}, this.getViewLifecycleOwner(), Lifecycle.State.RESUMED);
}
/** /**
* タスク完了確認ダイアログを表示 (子供モード用) * タスク完了確認ダイアログを表示 (子供モード用)
* *
* @param taskId タスクID
* @param taskName タスク名 * @param taskName タスク名
* @return OKボタンが押されたかどうか
*/ */
private boolean showConfirmDialog(String taskName) { private void showConfirmDialog(String taskId, 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();
selection.set(true); taskData.recordTaskCompletion(taskId, childId);
}) })
.setNegativeButton("いいえ", (dialog, which) -> { .setNegativeButton("いいえ", (dialog, which) -> {
dialog.dismiss(); dialog.dismiss();
selection.set(false); taskData.recordTaskCompletion(taskId, childId);
}) })
.show(); .show();
return selection.get();
} }
/** /**
@ -213,19 +348,27 @@ public class CommonHomeFragment extends Fragment {
return taskData.getTasks(new TaskItemModelCallback() { return taskData.getTasks(new TaskItemModelCallback() {
@Override @Override
public void onUnchanged() { public void onUnchanged() {
// Do nothing
} }
@Override @Override
public void onUpdated(List<TaskItemModel> taskItem) { public void onUpdated(List<TaskItemModel> taskItem) { // Workaround
taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
taskListItemAdapter.setTaskItemModelList(taskItem);
taskListItemAdapter.notifyItemRangeInserted(0, taskItem.size());
} }
@Override @Override
public void onFailed(String message) { public void onFailed(String message) {
// TODO: ユーザーに丁寧に通知
Toast.makeText(requireContext(), "タスク情報の取得に失敗しました", Toast.LENGTH_SHORT).show(); // Workaround
} }
}).thenAccept(taskItemModel -> { }).thenAccept(taskItemModel -> {
taskListItemAdapter.setTaskItemModelList(taskItemModel);
requireActivity().runOnUiThread(() -> { requireActivity().runOnUiThread(() -> {
taskListItemAdapter.notifyDataSetChanged(); // taskListItemAdapter.notifyItemRangeRemoved(0, taskListItemAdapter.getItemCount());
// taskListItemAdapter.setTaskItemModelList(taskItemModel);
// taskListItemAdapter.notifyItemRangeInserted(0, taskItemModel.size());
taskListItemAdapter.setTaskItemModelList(taskItemModel);
taskListItemAdapter.notifyItemRangeChanged(0, taskItemModel.size());
}); });
}); });
} }
@ -235,23 +378,71 @@ public class CommonHomeFragment extends Fragment {
* @return CompletableFuture<Void> * @return CompletableFuture<Void>
*/ */
private CompletableFuture<Void> updateCalender() { private CompletableFuture<Void> updateCalender() {
// TODO: タスクの完了状況をカレンダーに表示 return rewardData.getRewardHistoryList().thenAccept(historyModels -> {
return CompletableFuture.completedFuture(null); historyModels.forEach(historyModel -> {
compactCalendarView.addEvent(new Event(Color.RED, historyModel.getRegisteredAt().getTime(), historyModel)); // debug
});
});
}
private void initCalender() {
compactCalendarView.setListener(new CompactCalendarView.CompactCalendarViewListener() {
@Override
public void onDayClick(Date date) { // Test
List<Event> events = compactCalendarView.getEvents(date);
ScrollView scrollView = new ScrollView(requireContext());
LinearLayout linearLayout = new LinearLayout(requireContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setPadding(96, 24, 96, 24);
scrollView.addView(linearLayout);
events.forEach(event -> {
TextView textView = new TextView(requireContext());
textView.setText(((HistoryModel) event.getData()).getTaskName() + " @ " + ((HistoryModel) event.getData()).getChildId());
textView.setPadding(0, 0, 0, 24);
linearLayout.addView(textView);
});
new MaterialAlertDialogBuilder(requireContext())
.setTitle("タスク一覧 (DEBUG)")
.setMessage(events.size() + "件のタスクが登録されています")
.setView(scrollView)
.setNeutralButton("閉じる", (dialog, which) -> dialog.dismiss())
.show();
}
@Override
public void onMonthScroll(Date date) {
// 0000年00月の形式に変換 getYear/getMonthは非推奨
calendarTitleTextView.setText(String.format("%d年%d月", date.getYear() + 1900, date.getMonth() + 1)); // 統合
}
});
// 初回
Date date = new Date();
calendarTitleTextView.setText(String.format("%d年%d月", date.getYear() + 1900, date.getMonth() + 1)); // 統合
calendarPrevButton.setOnClickListener(v -> {
compactCalendarView.scrollLeft();
});
calendarNextButton.setOnClickListener(v -> {
compactCalendarView.scrollRight();
});
} }
/** /**
* データを更新 (updateTaskInfoとupdateCalenderを並列実行) * データを更新 (updateTaskInfoとupdateCalenderを並列実行)
*/ */
private void updateData() { private void updateData() {
swipeRefreshLayout.setRefreshing(true); requireActivity().runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(true);
});
CompletableFuture.allOf(updateTaskInfo(), updateCalender()).thenRun(() -> { CompletableFuture.allOf(updateTaskInfo(), updateCalender()).thenRun(() -> {
// Workaround: リスト更新処理があまりにも重くてアニメーションが壊れるため requireActivity().runOnUiThread(() -> {
try { swipeRefreshLayout.setRefreshing(false);
Thread.sleep(500); });
} catch (InterruptedException e) {
// do nothing
}
swipeRefreshLayout.setRefreshing(false);
}); });
} }
@ -259,10 +450,21 @@ public class CommonHomeFragment extends Fragment {
* タスク追加ダイアログを表示 * タスク追加ダイアログを表示
*/ */
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("Placeholder") .setTitle("タスクを追加")
.setMessage("Placeholder") .setView(view)
.setPositiveButton("OK", (dialog, which) -> dialog.dismiss()) .setPositiveButton("追加", (dialog, which) -> {
EditText taskNameEditText = view.findViewById(R.id.addTaskNameEditText);
EditText taskRewardEditText = view.findViewById(R.id.addTaskRewardEditText);
TaskItemModel taskItemModel = new TaskItemModel();
taskItemModel.setName(taskNameEditText.getText().toString());
taskItemModel.setReward(Integer.parseInt(taskRewardEditText.getText().toString()));
taskData.addTask(taskItemModel).thenRun(this::updateData);
})
.setNegativeButton("キャンセル", (dialog, which) -> dialog.dismiss())
.show(); .show();
} }
} }

View File

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

View File

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

View File

@ -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: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

@ -11,22 +11,72 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.github.sundeepk.compactcalendarview.CompactCalendarView <LinearLayout
android:id="@+id/calendar" android:layout_width="match_parent"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_height="250dp" android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:compactCalendarBackgroundColor="#ffe95451"
app:compactCalendarCurrentDayBackgroundColor="#B71C1C"
app:compactCalendarCurrentSelectedDayBackgroundColor="#E57373"
app:compactCalendarMultiEventIndicatorColor="#fff"
app:compactCalendarTargetHeight="250dp"
app:compactCalendarTextColor="#fff"
app:compactCalendarTextSize="12sp"
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"
android:id="@+id/calendarContainer">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorSurface">
<ImageButton
android:id="@+id/calendarPrevButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/arrow_back_24px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/colorOnSurface"
android:padding="8dp"/>
<TextView
android:id="@+id/calendarTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorOnSurface"
android:textSize="20sp"
android:text="2000年1月"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/calendarNextButton"
app:layout_constraintStart_toEndOf="@+id/calendarPrevButton"
app:layout_constraintTop_toTopOf="parent"/>
<ImageButton
android:id="@+id/calendarNextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/arrow_forward_24px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/colorOnSurface"
android:padding="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.github.sundeepk.compactcalendarview.CompactCalendarView
android:id="@+id/calendar"
android:layout_width="fill_parent"
android:layout_height="300dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:compactCalendarBackgroundColor="@color/colorSurface"
app:compactCalendarCurrentDayBackgroundColor="@color/colorSurfaceVariant"
app:compactCalendarCurrentSelectedDayBackgroundColor="@color/colorSurfaceVariant"
app:compactCalendarMultiEventIndicatorColor="@color/colorOnSurface"
app:compactCalendarTextColor="@color/colorOnSurface"
app:compactCalendarTextSize="12sp"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/taskListRecyclerView" android:id="@+id/taskListRecyclerView"
@ -35,7 +85,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/calendar" /> app:layout_constraintTop_toBottomOf="@+id/calendarContainer" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CommonSelectChildFragment" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
android:text="使用するお子様を選択"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/selectShowChildListRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="128dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:padding="8px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/childNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="PLACEHOLDER"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<!-- ImageButtonに変更? -->
<Button
android:id="@+id/selectButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="表示" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/toggle_calendar"
android:icon="@drawable/calendar_month_24px"
app:showAsAction="ifRoom"
android:title="カレンダー表示切り替え" />
</menu>

View File

@ -10,6 +10,9 @@
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

@ -3,11 +3,28 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feature_common_parent_child_navigation" android:id="@+id/feature_common_parent_child_navigation"
app:startDestination="@id/commonHomeFragmentParentChild"> app:startDestination="@id/commonSelectChildFragment">
<fragment <fragment
android:id="@+id/commonHomeFragmentParentChild" android:id="@+id/commonHomeFragmentParentChild"
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" />
<fragment
android:id="@+id/commonSelectChildFragment"
android:name="one.nem.kidshift.feature.common.CommonSelectChildFragment"
android:label="fragment_common_select_child"
tools:layout="@layout/fragment_common_select_child" >
<action
android:id="@+id/action_commonSelectChildFragment_to_commonHomeFragmentParentChild"
app:destination="@id/commonHomeFragmentParentChild" >
<argument
android:name="childId"
app:argType="string" />
<argument
android:name="isChildMode"
app:argType="boolean"
android:defaultValue="true" />
</action>
</fragment>
</navigation> </navigation>

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,9 +12,17 @@ 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 {
@ -27,14 +35,21 @@ 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
@ -77,28 +92,114 @@ 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();
fabManager.hide(); // updateItems();
// fabManager.hide();
toolBarManager.setTitle("ウォレット");
toolBarManager.setSubtitle(null);
} }
} }

View File

@ -26,7 +26,10 @@ 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
@ -42,9 +45,14 @@ 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
@ -64,22 +72,50 @@ 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 tabAdapter = new TabAdapter(requireActivity()); tabAdapter = new TabAdapter(requireActivity());
// デバッグ用
List<ChildModel> childList = childData.getChildListDirect().join();
tabAdapter.setChildList(childList);
viewPager.setAdapter(tabAdapter); viewPager.setAdapter(tabAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { setupViewPager();
tab.setText(childList.get(position).getName());
}).attach();
return view; 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 static class TabAdapter extends FragmentStateAdapter {
private List<ChildModel> childList; private List<ChildModel> childList;
@ -104,4 +140,12 @@ 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,9 +16,12 @@
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:layout_marginTop="24dp" android:background="@color/colorSurface"
android:layout_marginBottom="24dp" android:backgroundTintMode="add"
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
@ -32,6 +35,7 @@
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
@ -40,7 +44,8 @@
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

@ -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

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.3.2" agp = "8.0.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,14 +9,16 @@ 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) { public HistoryModel(String id, String taskId, String taskName, String childId, Date registeredAt, int reward, boolean isPaid) {
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) { // 他モデルとのマージが必要なので
@ -76,4 +78,12 @@ 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

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="700"
android:fillAfter="false"
>
<translate
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="100%p"
android:toXDelta="0"
/>
<alpha
android:fromAlpha="0.5"
android:toAlpha="1"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
/>
</set>

View File

@ -0,0 +1,11 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300"/>
<translate
android:fromYDelta="-50%"
android:toYDelta="0"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"/>
</set>

View File

@ -0,0 +1,11 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300"/>
<translate
android:fromYDelta="0"
android:toYDelta="-50%"
android:duration="300"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>

View File

@ -0,0 +1,11 @@
<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"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<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"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M647,520L160,520L160,440L647,440L423,216L480,160L800,480L480,800L423,744L647,520Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<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,880Q167,880 143.5,856.5Q120,833 120,800L120,240Q120,207 143.5,183.5Q167,160 200,160L240,160L240,80L320,80L320,160L640,160L640,80L720,80L720,160L760,160Q793,160 816.5,183.5Q840,207 840,240L840,800Q840,833 816.5,856.5Q793,880 760,880L200,880ZM200,800L760,800Q760,800 760,800Q760,800 760,800L760,400L200,400L200,800Q200,800 200,800Q200,800 200,800ZM200,320L760,320L760,240Q760,240 760,240Q760,240 760,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM200,320L200,240Q200,240 200,240Q200,240 200,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM480,560Q463,560 451.5,548.5Q440,537 440,520Q440,503 451.5,491.5Q463,480 480,480Q497,480 508.5,491.5Q520,503 520,520Q520,537 508.5,548.5Q497,560 480,560ZM320,560Q303,560 291.5,548.5Q280,537 280,520Q280,503 291.5,491.5Q303,480 320,480Q337,480 348.5,491.5Q360,503 360,520Q360,537 348.5,548.5Q337,560 320,560ZM640,560Q623,560 611.5,548.5Q600,537 600,520Q600,503 611.5,491.5Q623,480 640,480Q657,480 668.5,491.5Q680,503 680,520Q680,537 668.5,548.5Q657,560 640,560ZM480,720Q463,720 451.5,708.5Q440,697 440,680Q440,663 451.5,651.5Q463,640 480,640Q497,640 508.5,651.5Q520,663 520,680Q520,697 508.5,708.5Q497,720 480,720ZM320,720Q303,720 291.5,708.5Q280,697 280,680Q280,663 291.5,651.5Q303,640 320,640Q337,640 348.5,651.5Q360,663 360,680Q360,697 348.5,708.5Q337,720 320,720ZM640,720Q623,720 611.5,708.5Q600,697 600,680Q600,663 611.5,651.5Q623,640 640,640Q657,640 668.5,651.5Q680,663 680,680Q680,697 668.5,708.5Q657,720 640,720Z"/>
</vector>

View File

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

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

View File

@ -0,0 +1,10 @@
<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="M640,480L720,560L720,640L520,640L520,880L480,920L440,880L440,640L240,640L240,560L320,480L320,200L280,200L280,120L680,120L680,200L640,200L640,480ZM354,560L606,560L560,514L560,200L400,200L400,514L354,560ZM480,560L480,560L480,560L480,560L480,560L480,560Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<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="M680,120L680,200L640,200L640,527L560,447L560,200L400,200L400,287L313,200L280,167L280,167L280,120L680,120ZM480,920L440,880L440,640L240,640L240,560L320,480L320,434L56,168L112,112L848,848L790,904L526,640L520,640L520,880L480,920ZM354,560L446,560L402,516L400,514L354,560ZM480,367L480,367L480,367L480,367ZM402,516L402,516L402,516L402,516Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<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"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L480,760L480,840L200,840ZM640,680L585,622L687,520L360,520L360,440L687,440L585,338L640,280L840,480L640,680Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<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="M280,840L280,760L440,760L440,636Q391,625 352.5,594.5Q314,564 296,518Q221,509 170.5,452.5Q120,396 120,320L120,280Q120,247 143.5,223.5Q167,200 200,200L280,200L280,120L680,120L680,200L760,200Q793,200 816.5,223.5Q840,247 840,280L840,320Q840,396 789.5,452.5Q739,509 664,518Q646,564 607.5,594.5Q569,625 520,636L520,760L680,760L680,840L280,840ZM280,432L280,280L200,280L200,320Q200,358 222,388.5Q244,419 280,432ZM480,560Q530,560 565,525Q600,490 600,440L600,200L360,200L360,440Q360,490 395,525Q430,560 480,560ZM680,432Q716,419 738,388.5Q760,358 760,320L760,280L680,280L680,432ZM480,380Q480,380 480,380Q480,380 480,380L480,380L480,380L480,380Q480,380 480,380Q480,380 480,380Z"/>
</vector>

View File

@ -38,6 +38,8 @@ dependencies {
implementation libs.com.google.dagger.hilt.android implementation libs.com.google.dagger.hilt.android
annotationProcessor libs.com.google.dagger.hilt.compiler annotationProcessor libs.com.google.dagger.hilt.compiler
implementation 'jp.wasabeef:recyclerview-animators:4.0.2'
// Gson // Gson
implementation libs.gson implementation libs.gson
} }

View File

@ -0,0 +1,51 @@
package one.nem.kidshift.utils;
import androidx.recyclerview.widget.RecyclerView;
import javax.inject.Inject;
import javax.inject.Singleton;
import jp.wasabeef.recyclerview.animators.FadeInAnimator;
import jp.wasabeef.recyclerview.animators.SlideInUpAnimator;
@Singleton
public class RecyclerViewAnimUtils {
@Inject
public RecyclerViewAnimUtils() {
}
// SlideUp
public void setSlideUpAnimation(RecyclerView recyclerView) {
recyclerView.setItemAnimator(new SlideInUpAnimator());
recyclerView.getItemAnimator().setAddDuration(300);
recyclerView.getItemAnimator().setRemoveDuration(100);
recyclerView.getItemAnimator().setMoveDuration(100);
recyclerView.getItemAnimator().setChangeDuration(100);
}
public void setSlideUpAnimation(RecyclerView recyclerView, int duration) {
recyclerView.setItemAnimator(new SlideInUpAnimator());
recyclerView.getItemAnimator().setAddDuration(duration);
recyclerView.getItemAnimator().setRemoveDuration(duration);
recyclerView.getItemAnimator().setMoveDuration(duration);
recyclerView.getItemAnimator().setChangeDuration(duration);
}
// Fade
public void setFadeAnimation(RecyclerView recyclerView) {
recyclerView.setItemAnimator(new FadeInAnimator());
recyclerView.getItemAnimator().setAddDuration(300);
recyclerView.getItemAnimator().setRemoveDuration(100);
recyclerView.getItemAnimator().setMoveDuration(100);
recyclerView.getItemAnimator().setChangeDuration(100);
}
public void setFadeAnimation(RecyclerView recyclerView, int duration) {
recyclerView.setItemAnimator(new FadeInAnimator());
recyclerView.getItemAnimator().setAddDuration(duration);
recyclerView.getItemAnimator().setRemoveDuration(duration);
recyclerView.getItemAnimator().setMoveDuration(duration);
recyclerView.getItemAnimator().setChangeDuration(duration);
}
}

View File

@ -0,0 +1,40 @@
package one.nem.kidshift.utils;
import androidx.appcompat.widget.Toolbar;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class ToolBarManager {
private Toolbar toolbar;
@Inject
public ToolBarManager() {
}
public void setToolbar(Toolbar toolbar) {
this.toolbar = toolbar;
}
private void checkToolbar() {
if (toolbar == null) {
throw new IllegalStateException("Toolbar is not set");
}
}
public void setTitle(String title) {
checkToolbar();
toolbar.setTitle(title);
}
public void setSubtitle(String subtitle) {
checkToolbar();
toolbar.setSubtitle(subtitle);
}
public Toolbar getToolbar() {
return toolbar;
}
}