feature/wallet #146

Merged
Fujimatsu merged 63 commits from feature/wallet into main 2024-07-08 03:36:09 +00:00
41 changed files with 991 additions and 55 deletions

View File

@ -17,6 +17,7 @@
<option value="$PROJECT_DIR$/feature/debug" />
<option value="$PROJECT_DIR$/feature/parent" />
<option value="$PROJECT_DIR$/feature/setting" />
<option value="$PROJECT_DIR$/feature/wallet" />
<option value="$PROJECT_DIR$/model" />
<option value="$PROJECT_DIR$/shared" />
<option value="$PROJECT_DIR$/utils" />

View File

@ -46,6 +46,7 @@ dependencies {
implementation project(':feature:child')
implementation project(':feature:setting')
implementation project(':feature:common')
implementation project(':feature:wallet')
implementation project(':data')

View File

@ -20,4 +20,9 @@
android:id="@+id/feature_setting_navigation"
android:icon="@drawable/settings_24px"
android:title="Setting" />
<item
android:id="@+id/feature_wallet_parent_navigation"
android:icon="@drawable/wallet_24px"
android:title="Wallet" />
</menu>

View File

@ -8,4 +8,5 @@
<include app:graph="@navigation/feature_child_navigation" />
<include app:graph="@navigation/feature_parent_navigation" />
<include app:graph="@navigation/feature_setting_navigation" />
<include app:graph="@navigation/feature_wallet_parent_navigation" />
</navigation>

View File

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

View File

@ -7,5 +7,5 @@ public interface RewardData {
* 現時点の合計報酬額を取得する
* @return Integer 合計報酬額
*/
CompletableFuture<Integer> getTotalReward();
CompletableFuture<Integer> getTotalReward(String childId);
}

View File

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

View File

@ -1,27 +0,0 @@
package one.nem.kidshift.data.impl;
import com.github.javafaker.Faker;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.utils.KSLogger;
public class RewardDataDummyImpl implements RewardData {
private final Faker faker;
@Inject
public RewardDataDummyImpl() {
faker = new Faker();
// logger.setTag("RewardDataDummyImpl");
}
@Override
public CompletableFuture<Integer> getTotalReward() {
return CompletableFuture.supplyAsync(() -> faker.number().numberBetween(0, 1000));
}
}

View File

@ -0,0 +1,35 @@
package one.nem.kidshift.data.impl;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.data.UserSettings;
import one.nem.kidshift.data.room.utils.CacheWrapper;
import one.nem.kidshift.model.HistoryModel;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
public class RewardDataImpl implements RewardData {
private final UserSettings userSettings;
private final KSActions ksActions;
private final CacheWrapper cacheWrapper;
private final KSLogger logger;
@Inject
public RewardDataImpl(KSLoggerFactory ksLoggerFactory, CacheWrapper cacheWrapper, UserSettings userSettings, KSActions ksActions) {
this.userSettings = userSettings;
this.ksActions = ksActions;
this.cacheWrapper = cacheWrapper;
this.logger = ksLoggerFactory.create("RewardDataImpl");
}
@Override
public CompletableFuture<Integer> getTotalReward(String childId) {
return CompletableFuture.supplyAsync(() -> ksActions.syncHistory(childId).join().stream().mapToInt(HistoryModel::getReward).sum());
}
}

View File

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

View File

@ -8,6 +8,7 @@ import one.nem.kidshift.data.retrofit.model.child.ChildResponse;
import one.nem.kidshift.data.retrofit.model.parent.ParentInfoResponse;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthRequest;
import one.nem.kidshift.data.retrofit.model.parent.auth.ParentAuthResponse;
import one.nem.kidshift.data.retrofit.model.task.HistoryListResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskAddRequest;
import one.nem.kidshift.data.retrofit.model.task.TaskListResponse;
import one.nem.kidshift.data.retrofit.model.task.TaskResponse;
@ -134,4 +135,8 @@ public interface KidShiftApiService {
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<ChildLoginCodeResponse> issueLoginCode(@Path("id") String id);
@GET("/task/history/{childId}")
@Headers(AuthorizationInterceptor.HEADER_PLACEHOLDER)
Call<HistoryListResponse> getHistory(@Path("childId") String childId);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
feature/wallet/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

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

View File

21
feature/wallet/proguard-rules.pro vendored Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,90 @@
package one.nem.kidshift.wallet;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.KSActions;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.utils.FabManager;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint
public class WalletContentFragment extends Fragment {
private static final String ARG_CHILD_ID = "childId";
@Inject
KSLoggerFactory loggerFactory;
@Inject
RewardData rewardData;
@Inject
FabManager fabManager;
private KSLogger logger;
private String childId;
private TextView totalRewardTextView;
public WalletContentFragment() {
// Required empty public constructor
}
public static WalletContentFragment newInstance(String childId) {
WalletContentFragment fragment = new WalletContentFragment();
Bundle args = new Bundle();
args.putString(ARG_CHILD_ID, childId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
childId = getArguments().getString(ARG_CHILD_ID);
}
logger = loggerFactory.create("WalletMainFragment");
logger.debug("Received parameter: " + childId);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_wallet_content, container, false);
totalRewardTextView = view.findViewById(R.id.totalRewardTextView);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
private void updateTotalReward() {
rewardData.getTotalReward(childId).thenAccept(totalReward -> {
logger.debug("Total reward: " + totalReward);
totalRewardTextView.setText(String.valueOf(totalReward) + "");
}).exceptionally(throwable -> {
logger.error("Failed to get total reward: " + throwable.getMessage());
return null;
});
}
@Override
public void onResume() {
super.onResume();
updateTotalReward();
fabManager.hide();
}
}

View File

@ -0,0 +1,107 @@
package one.nem.kidshift.wallet;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.data.ChildData;
import one.nem.kidshift.data.RewardData;
import one.nem.kidshift.model.ChildModel;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.factory.KSLoggerFactory;
@AndroidEntryPoint
public class WalletParentWrapperFragment extends Fragment {
@Inject
KSLoggerFactory loggerFactory;
private KSLogger logger;
@Inject
ChildData childData;
@Inject
RewardData rewardData;
private TabLayout tabLayout;
private ViewPager2 viewPager;
public WalletParentWrapperFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_wallet_parent_wrapper, container, false);
tabLayout = view.findViewById(R.id.tabLayout);
viewPager = view.findViewById(R.id.viewPager);
TabAdapter tabAdapter = new TabAdapter(requireActivity());
// デバッグ用
List<ChildModel> childList = childData.getChildListDirect().join();
tabAdapter.setChildList(childList);
viewPager.setAdapter(tabAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
tab.setText(childList.get(position).getName());
}).attach();
return view;
}
private static class TabAdapter extends FragmentStateAdapter {
private List<ChildModel> childList;
public void setChildList(List<ChildModel> childList) {
this.childList = childList;
}
public TabAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull
@Override
public Fragment createFragment(int position) {
return WalletContentFragment.newInstance(childList.get(position).getId());
}
@Override
public int getItemCount() {
return childList == null ? 0 : childList.size();
}
}
}

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WalletContentFragment"
android:id="@+id/swipeRefreshLayout">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toTopOf="@+id/historyItemRecyclerView"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="今月の支払い総額"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textSize="20sp" />
<TextView
android:id="@+id/totalRewardTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="0000円"
android:textAppearance="@style/TextAppearance.AppCompat.Display2" />
</LinearLayout>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/historyItemRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frameLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -0,0 +1,28 @@
<?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=".WalletParentWrapperFragment">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feature_wallet_child_navigation"
app:startDestination="@id/walletMainFragment">
<fragment
android:id="@+id/walletMainFragment"
android:name="one.nem.kidshift.wallet.WalletContentFragment"
android:label="fragment_wallet_main"
tools:layout="@layout/fragment_wallet_content" />
</navigation>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feature_wallet_parent_navigation"
app:startDestination="@id/walletParentWrapperFragment">
<fragment
android:id="@+id/walletParentWrapperFragment"
android:name="one.nem.kidshift.wallet.WalletParentWrapperFragment"
android:label="fragment_wallet_parent_wrapper"
tools:layout="@layout/fragment_wallet_parent_wrapper" />
</navigation>

View File

@ -0,0 +1,4 @@
<resources>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View File

@ -0,0 +1,17 @@
package one.nem.kidshift.wallet;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -0,0 +1,79 @@
package one.nem.kidshift.model;
import java.util.Date;
public class HistoryModel {
private String id;
private String taskId;
private String taskName;
private String childId;
private Date registeredAt;
private int reward;
public HistoryModel(String id, String taskId, String taskName, String childId, Date registeredAt, int reward) {
this.id = id;
this.taskId = taskId;
this.taskName = taskName;
this.childId = childId;
this.registeredAt = registeredAt;
this.reward = reward;
}
public HistoryModel(String id, String taskId, String childId, Date registeredAt) { // 他モデルとのマージが必要なので
this.id = id;
this.taskId = taskId;
this.childId = childId;
this.registeredAt = registeredAt;
}
public HistoryModel() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getChildId() {
return childId;
}
public void setChildId(String childId) {
this.childId = childId;
}
public Date getRegisteredAt() {
return registeredAt;
}
public void setRegisteredAt(Date registeredAt) {
this.registeredAt = registeredAt;
}
public int getReward() {
return reward;
}
public void setReward(int reward) {
this.reward = reward;
}
}

View File

@ -34,3 +34,4 @@ include ':data'
include ':model'
include ':shared'
include ':feature:common'
include ':feature:wallet'

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="M479.62,788.38Q466.77,788.38 453.81,783.77Q440.85,779.15 431,769.31L373.54,717.08Q267.16,620.08 183.58,526.5Q100,432.92 100,326Q100,240.85 157.42,183.42Q214.85,126 300,126Q348.38,126 395.58,148.31Q442.77,170.62 480,220.77Q517.23,170.62 564.42,148.31Q611.62,126 660,126Q745.15,126 802.58,183.42Q860,240.85 860,326Q860,434.08 775,528.73Q690,623.38 585.46,717.69L528.61,769.31Q518.77,779.15 505.62,783.77Q492.46,788.38 479.62,788.38ZM451.23,281.54Q418.77,232.08 382.88,209.04Q347,186 300,186Q240,186 200,226Q160,266 160,326Q160,374.15 191.04,426.69Q222.08,479.23 268.96,531.15Q315.84,583.08 370.54,632.61Q425.23,682.15 471.92,724.69Q475.38,727.77 480,727.77Q484.62,727.77 488.08,724.69Q534.77,682.15 589.46,632.61Q644.16,583.08 691.04,531.15Q737.92,479.23 768.96,426.69Q800,374.15 800,326Q800,266 760,226Q720,186 660,186Q613,186 577.12,209.04Q541.23,232.08 508.77,281.54Q503.69,289.23 496,293.08Q488.31,296.92 480,296.92Q471.69,296.92 464,293.08Q456.31,289.23 451.23,281.54ZM480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Q480,457.46 480,457.46Z"/>
</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,780Q182.08,780 141.04,738.96Q100,697.92 100,640L100,320Q100,262.08 141.04,221.04Q182.08,180 240,180L720,180Q777.92,180 818.96,221.04Q860,262.08 860,320L860,640Q860,697.92 818.96,738.96Q777.92,780 720,780L240,780ZM240,330L720,330Q742.77,330 762.96,336.54Q783.15,343.08 800,356.39L800,320Q800,287 776.5,263.5Q753,240 720,240L240,240Q207,240 183.5,263.5Q160,287 160,320L160,356.39Q176.85,343.08 197.04,336.54Q217.23,330 240,330ZM163.69,449.62L612.92,558.77Q621.15,560.77 629.58,558.96Q638,557.15 644.85,551.54L788.85,430.54Q779,412.46 760.65,401.23Q742.31,390 720,390L240,390Q211.69,390 190.85,406.58Q170,423.15 163.69,449.62Z"/>
</vector>