Skip to content

Commit

Permalink
Merge pull request #133 from sports-club-hanzan/feature/refactor
Browse files Browse the repository at this point in the history
歩数がズレることの補正 / 歩数が初期化されないことの抑制
  • Loading branch information
rikeda71 authored Sep 9, 2023
2 parents 0a493e8 + ea6a0f3 commit 682665e
Show file tree
Hide file tree
Showing 32 changed files with 284 additions and 227 deletions.
8 changes: 4 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ if (keystorePropertiesFile.exists()) {
android {
// https://firebase.google.com/docs/android/setup?hl=ja#register-app => 31
// 2022/10現在、最新のバージョン(Android13)
compileSdkVersion 33
compileSdkVersion 34
ndkVersion "25.2.9519653"

compileOptions {
Expand All @@ -88,10 +88,10 @@ android {
// ref. https://firebase.google.com/docs/android/setup?hl=ja#register-app => minSdkVersion: 19
// ref. https://pub.dev/packages/google_maps_flutter => minSdkVersion: 20
// ref. https://twitter.com/_mono/status/1509452309398695936
// GoogleFit が Android5以降(sdkVersion = 21)対応なので、HealthConnect が sdkVersion = 26 対応なので
minSdkVersion 26
// GoogleFit が Android5以降(sdkVersion = 21)対応なので、HealthConnect が sdkVersion = 28 対応なので
minSdkVersion 28
// 2022/10現在、最新のバージョン(Android13)
targetSdkVersion 33
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// 環境ごとにパッケージ名を修正
Expand Down
22 changes: 15 additions & 7 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.virtualpilgrimage">

<!-- permissions -->
<uses-permission android:name="android.permission.INTERNET" tools:ignore="ManifestOrder" />
<!-- health 用-->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>

<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
Expand All @@ -27,6 +34,14 @@
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
<activity
android:name=".PermissionsRationaleActivity"
android:exported="true">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>

<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
Expand All @@ -47,13 +62,6 @@
</intent>
</queries>

<!-- permissions -->
<uses-permission android:name="android.permission.INTERNET" tools:ignore="ManifestOrder" />
<!-- health 用-->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>

<!-- タブレットでは利用できない -->
<supports-screens
android:smallScreens="true"
Expand Down
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
cloud_firestore: eb6bea100a5957ac15e911a317a52738813e72ef
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
Firebase: facd334e557a979bd03a0b58d90fd56b52b8aba0
firebase_analytics: 1a5ad75876257318ba5fdc6bf7aae73b6e98d0cf
firebase_auth: 9905bc3d82328b5050a8b7cb410a959f150b6549
Expand Down
2 changes: 1 addition & 1 deletion lib/application/auth/sign_in_interactor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class SignInInteractor extends SignInUsecase {

@override
Future<void> logout() async {
// TODO(s14t284): firebase を直参照ではなく、もっといい方法を考える
// TODO(rikeda71): firebase を直参照ではなく、もっといい方法を考える
await _firebaseAuth.signOut();
}

Expand Down
3 changes: 2 additions & 1 deletion lib/application/health/health_gateway.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import 'package:virtualpilgrimage/infrastructure/user/flutter_health_gateway.dar
import 'package:virtualpilgrimage/logger.dart';

final healthFactoryProvider = Provider<HealthFactory>(
(_) => HealthFactory(),
// TODO(rikeda71): いずれは health connect に対応する必要がある
(_) => HealthFactory(useHealthConnectIfAvailable: false),
);

final healthGatewayProvider = Provider<HealthGateway>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:math';

import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:logger/logger.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:virtualpilgrimage/application/health/daily_health_log_repository.dart';
import 'package:virtualpilgrimage/application/health/health_gateway.dart';
import 'package:virtualpilgrimage/application/pilgrimage/temple_repository.dart';
Expand Down Expand Up @@ -112,64 +111,69 @@ class UpdatePilgrimageProgressInteractor extends UpdatePilgrimageProgressUsecase
);

/// 1. 進捗を更新するために必要な情報を取得
// 最大で2回外部通信する必要があるため、並列でまとめて実行
// 現在、ユーザが目指している札所の情報と最終更新時間からのユーザのヘルスケア情報を取得
late TempleInfo nowTargetTemple;
late final HealthAggregationResult healthAggregationResult;
late final HealthByPeriod? todayHealth;
late final HealthByPeriod? yesterdayHealth;
{
final today = tz.TZDateTime(tz.getLocation('Asia/Tokyo'), now.year, now.month, now.day);
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
// 通信時間短縮のため、並列でまとめて実行
await Future.wait(<Future<void>>[
_templeRepository.getTempleInfo(nextPilgrimageId).then((value) => nowTargetTemple = value),
// 今日のヘルスケア情報を取得
_healthRepository
.aggregateHealthByPeriod(
from: today,
to: now,
)
.aggregateHealthByPeriod(from: today, to: now)
.then((value) => todayHealth = value.eachDay[today]),
// 「前回更新した時刻の開始地点 or 今日の00:00」 ~ 現在時刻で集計
// 昨日のヘルスケア情報を取得
_healthRepository
.aggregateHealthByPeriod(
from: lastProgressUpdatedAt.isBefore(today) ? lastProgressUpdatedAt : today,
to: now,
from: yesterday,
to: today.subtract(const Duration(microseconds: 1)),
)
.then((value) => yesterdayHealth = value.eachDay[yesterday]),
// 前回更新した時刻の開始地点 ~ 現在時刻で集計
_healthRepository
.aggregateHealthByPeriod(from: lastProgressUpdatedAt, to: now)
.then((value) => healthAggregationResult = value),
]);
_logger.d(
_logger.i(
'got info for updating pilgrimage progress '
'[health][$healthAggregationResult]'
'[todayHealth][$todayHealth]'
'[yesterdayHealth][$yesterdayHealth]'
'[nowTempleInfo][$nowTargetTemple]',
);
// バリデーションで更新する必要がない場合は早期終了
if (!healthAggregationResult.total.validate()) {
final msg =
'no health data [userId][${user.id}][from][$lastProgressUpdatedAt][to][$now][result][$healthAggregationResult]';
if (!healthAggregationResult.total.validate() && !(todayHealth?.validate() ?? false)) {
final msg = 'no health data [userId][${user.id}]'
'[from][$lastProgressUpdatedAt]'
'[to][$now]'
'[todayHealth][$todayHealth]'
'[yesterdayHealth][$yesterdayHealth]'
'[result][$healthAggregationResult]';
_logger.i(msg);
unawaited(_crashlytics.log(msg));
return user;
}
}

/// 2. 最後に保存した日毎のヘルスケア情報を取得
/// 3 で上書きされるので、この時点で保持しておく
final lastHealth = await _dailyHealthLogRepository.find(user.id, lastProgressUpdatedAt);

/// 3. 非同期でユーザのヘルスケア情報を更新
/// 2. 非同期でユーザのヘルスケア情報を更新
final updatedUser = await _updateUserHealth(
user: user,
healthAggregationResult: healthAggregationResult,
todayHealth: todayHealth,
yesterdayHealth: yesterdayHealth,
now: now,
);

/// 4. 移動距離 > 次の札所までの距離 の間、で移動距離を減らしながら次に目指すべき札所を導出する
/// 3. 移動距離 > 次の札所までの距離 の間、で移動距離を減らしながら次に目指すべき札所を導出する
return _updateUserPilgrimageProgress(
user: updatedUser,
totalDistance: healthAggregationResult.total.distance,
nowTargetTemple: nowTargetTemple,
reachedPilgrimageIdList: reachedPilgrimageIdList,
lastHealth: lastHealth,
now: now,
);
}
Expand All @@ -187,11 +191,14 @@ class UpdatePilgrimageProgressInteractor extends UpdatePilgrimageProgressUsecase
/// ヘルスケア情報を更新する
/// [user] ユーザ情報
/// [healthAggregationResult] 集計したヘルスケア情報
/// [todayHealth] 今日のヘルスケア情報
/// [yesterdayHealth] 昨日のヘルスケア情報
/// [now] 現在時刻
Future<VirtualPilgrimageUser> _updateUserHealth({
required VirtualPilgrimageUser user,
required HealthAggregationResult healthAggregationResult,
required HealthByPeriod? todayHealth,
required HealthByPeriod? yesterdayHealth,
required DateTime now,
}) async {
var updatedUser = user;
Expand All @@ -200,21 +207,31 @@ class UpdatePilgrimageProgressInteractor extends UpdatePilgrimageProgressUsecase
for (final e in healthAggregationResult.eachDay.entries) {
final key = e.key;
final value = e.value;
final yesterday = now.subtract(const Duration(days: 1));
final isToday = key.year == now.year && key.month == now.month && key.day == now.day;

final isYesterday =
key.year == yesterday.year && key.month == yesterday.month && key.day == yesterday.day;
late final HealthByPeriod trueValue;
if (isToday) {
trueValue = todayHealth ?? value;
} else if (isYesterday) {
trueValue = yesterdayHealth ?? value;
} else {
trueValue = value;
}
// 日毎のヘルスケア情報を書き込む
// 仮に情報が存在する場合も上書きする
final target = DailyHealthLog.createFromHealthByPeriod(
userId: user.id,
day: tz.TZDateTime(tz.getLocation('Asia/Tokyo'), key.year, key.month, key.day),
healthByPeriod: isToday ? todayHealth ?? value : value,
day: DateTime(key.year, key.month, key.day),
healthByPeriod: trueValue,
);
_logger.d('save health [target][$target][date][$key]');
unawaited(
_dailyHealthLogRepository.update(target).onError(_crashlytics.recordError),
);
if (isToday) {
final userHealth = user.health;
final userHealth = updatedUser.health;
// 今日の日付なら、health のなかに情報を入れておく
final HealthInfo newHealthByPeriod = userHealth != null
? userHealth.copyWith(
Expand All @@ -227,8 +244,26 @@ class UpdatePilgrimageProgressInteractor extends UpdatePilgrimageProgressUsecase
week: HealthByPeriod.getDefault(),
month: HealthByPeriod.getDefault(),
updatedAt: now,
totalSteps: target.steps,
totalDistance: target.distance,
totalSteps: healthAggregationResult.total.steps,
totalDistance: healthAggregationResult.total.distance,
);
updatedUser = user.copyWith(health: newHealthByPeriod);
} else if (isYesterday) {
final userHealth = updatedUser.health;
// 昨日の日付なら、health のなかに情報を入れておく
final HealthInfo newHealthByPeriod = userHealth != null
? userHealth.copyWith(
yesterday: target.toHealthByPeriod(),
updatedAt: now,
)
: HealthInfo(
today: HealthByPeriod.getDefault(),
yesterday: target.toHealthByPeriod(),
week: HealthByPeriod.getDefault(),
month: HealthByPeriod.getDefault(),
updatedAt: now,
totalSteps: healthAggregationResult.total.steps,
totalDistance: healthAggregationResult.total.distance,
);
updatedUser = user.copyWith(health: newHealthByPeriod);
}
Expand All @@ -242,27 +277,19 @@ class UpdatePilgrimageProgressInteractor extends UpdatePilgrimageProgressUsecase
/// [totalDistance] 集計した合計の移動距離
/// [nowTargetTemple] 現在目標にしている札所の情報
/// [reachedPilgrimageIdList] 到達した札所の番号一覧
/// [lastHealth] 最後に保存を実行した日の日毎のヘルスケア情報
/// [now] 現在時刻
Future<VirtualPilgrimageUser> _updateUserPilgrimageProgress({
required VirtualPilgrimageUser user,
required int totalDistance,
required TempleInfo nowTargetTemple,
required List<int> reachedPilgrimageIdList,
required DailyHealthLog? lastHealth,
required DateTime now,
}) async {
int lap = user.pilgrimage.lap;
int nextPilgrimageId = user.pilgrimage.nowPilgrimageId;
// 次の札所に向かうまでの移動距離を格納する変数
// お遍路の進捗の更新のために利用
/// 現在までに移動した距離 + 集計によって得られた合計移動距離 - 最後に実行した日の移動距離
/// 集計は最後に実行した日の 00:00 地点から現在時刻までで距離を取得する
/// 最後に実行した時刻の距離を引くことで、「現在までに移動した距離 + 最後に実行した時刻 ~ 現在時刻までの移動距離」という計算になる
int movingDistance = user.pilgrimage.movingDistance +
totalDistance -
(lastHealth != null ? lastHealth.distance : 0);
movingDistance = max(movingDistance, 0);
int movingDistance = max(user.pilgrimage.movingDistance + totalDistance, 0);
{
_logger.d(
'calc pilgrimage progress '
Expand Down
6 changes: 2 additions & 4 deletions lib/domain/customizable_date_time.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import 'dart:core';

import 'package:timezone/timezone.dart' as tz;

// Testable な DateTime
// DateTime.now() のテスト時に固定した時刻を返せるようにするために利用
// プロダクションコードでは CustomizableDateTime.current を呼び出す
Expand All @@ -11,10 +9,10 @@ extension CustomizableDateTime on DateTime {
static DateTime? _customTime;

static DateTime get current {
return _customTime ?? tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
return _customTime ?? DateTime.now();
}

static set customTime(DateTime customTime) {
_customTime = tz.TZDateTime.from(customTime, tz.getLocation('Asia/Tokyo'));
_customTime = customTime;
}
}
2 changes: 1 addition & 1 deletion lib/domain/user/health/health_by_period.codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HealthByPeriod with _$HealthByPeriod {
_$HealthByPeriodFromJson(json);

bool validate() {
return steps > 0 && distance > 0 && burnedCalorie > 0;
return steps > 0 && distance > 0;
}

static const HealthByPeriod _default = HealthByPeriod(
Expand Down
6 changes: 2 additions & 4 deletions lib/domain/user/virtual_pilgrimage_user.codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:virtualpilgrimage/domain/customizable_date_time.dart';
import 'package:virtualpilgrimage/domain/helper/firestore_timestamp_converter.dart';
import 'package:virtualpilgrimage/domain/pilgrimage/pilgrimage_info.codegen.dart';
Expand Down Expand Up @@ -215,9 +214,8 @@ class VirtualPilgrimageUser with _$VirtualPilgrimageUser {
/// プロフィール表示用の情報に変換
VirtualPilgrimageUser convertForProfile() {
final now = CustomizableDateTime.current;
final location = tz.getLocation('Asia/Tokyo');
final currentDate = tz.TZDateTime(location, now.year, now.month, now.day);
final updatedDate = tz.TZDateTime(location, updatedAt.year, updatedAt.month, updatedAt.day);
final currentDate = DateTime(now.year, now.month, now.day);
final updatedDate = DateTime(updatedAt.year, updatedAt.month, updatedAt.day);
// 更新日が今日より前の場合
if (updatedDate.isBefore(currentDate)) {
/// 更新日が昨日の場合
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class EmailAndPasswordAuthRepository extends AuthRepository {
handleCodeInApp: false,
androidInstallApp: true,
androidPackageName: packageName,
// TODO(s14t284): iOSでのテストを実施する
// TODO(rikeda71): iOSでのテストを実施する
iOSBundleId: packageName,
),
);
Expand Down
Loading

0 comments on commit 682665e

Please sign in to comment.