걸음 수 Health Connect·HealthKit · 위치 퀘스트 사진·QR 인증 · 행성 형태 확정
This commit is contained in:
@@ -8,9 +8,11 @@
|
||||
/// 생성자로 다른 구현을 넣는다 — 센서 없는 환경에서도 옮기는 규칙을 검증할 수 있어야 한다.
|
||||
library;
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:health/health.dart';
|
||||
import 'package:pedometer/pedometer.dart';
|
||||
import 'package:sensors_plus/sensors_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
@@ -308,6 +310,64 @@ Future<PlaceFix> devicePlaceFix() async {
|
||||
/// 권한을 영구 거부한 사용자를 설정 화면으로 보낸다. 화면의 「설정 열기」가 부른다.
|
||||
Future<void> openQuestPermissionSettings() => openAppSettings();
|
||||
|
||||
/// 오늘(KST 0시~지금) 걸은 수를 내는 함수. 쓸 수 없으면 null — 기기 센서([StepCountProbe])로 대체한다.
|
||||
typedef TodayStepsProbe = Future<int?> Function();
|
||||
|
||||
/// 오늘 0시(KST) — 서버 `dayKey()` 와 같은 날짜 경계.
|
||||
DateTime kstMidnight(DateTime at) {
|
||||
final kst = at.toUtc().add(const Duration(hours: 9));
|
||||
return DateTime.utc(kst.year, kst.month, kst.day).subtract(const Duration(hours: 9));
|
||||
}
|
||||
|
||||
/// 권한 확인은 앱이 떠 있는 동안 한 번만 한다. 거부되면 다음 실행에서 다시 묻는다.
|
||||
Future<bool>? _healthAuthorized;
|
||||
Health? _health;
|
||||
|
||||
/// 오늘 걸음 — **Health Connect(Android) · HealthKit(iOS)** 의 그 날 0시(KST)부터 지금까지 합계.
|
||||
///
|
||||
/// 견적서 「만보기(Health API)」 · 별첨 1 ③-1 「Health 권한 동의 → 걸음 집계」 · 정책 원장 **B-26**
|
||||
/// 「권한 거부 시 앱 자체 집계 전환」. 기기 걸음 센서는 앱을 연 뒤부터만 세므로(앱 열기 전 걸음 누락 —
|
||||
/// 대조표 B-02) 이 값이 1차 경로이고, Health 를 쓸 수 없을 때(Health Connect 미설치 · 권한 거부 ·
|
||||
/// HealthKit 미지원)만 null 을 내 센서 경로로 넘긴다. 판정은 여전히 서버가 한다.
|
||||
///
|
||||
/// 🟡 **임시값 — 회신 대기**(해석 확인 요청 영역B Q-B-09): 사용자가 건강 앱에 **직접 입력한 걸음은
|
||||
/// 빼고** 센다(`includeManualEntry: false`). 자료에 정의가 없고, 넣으면 손으로 숫자를 적어 구간을
|
||||
/// 받는다. Android 는 기록 앱이 입력 방식을 남기지 않으면 걸러지지 않는다(플러그인 문서).
|
||||
///
|
||||
/// ponytail: iOS 는 읽기 권한 거부를 알려 주지 않는다(HealthKit 개인정보 정책 — 거부해도 0 이 온다).
|
||||
/// 그래서 iOS 에서 0 이면 센서 경로로 넘긴다. 실제로 0 걸음인 날은 센서도 0 이라 결과가 같다.
|
||||
Future<int?> deviceTodaySteps() async {
|
||||
try {
|
||||
final health = _health ??= Health();
|
||||
final ok = await (_healthAuthorized ??= () async {
|
||||
await health.configure();
|
||||
if (Platform.isAndroid &&
|
||||
await health.getHealthConnectSdkStatus() != HealthConnectSdkStatus.sdkAvailable) {
|
||||
return false;
|
||||
}
|
||||
const types = [HealthDataType.STEPS];
|
||||
const access = [HealthDataAccess.READ];
|
||||
return await health.hasPermissions(types, permissions: access) == true ||
|
||||
await health.requestAuthorization(types, permissions: access);
|
||||
}());
|
||||
if (!ok) {
|
||||
_healthAuthorized = null;
|
||||
return null;
|
||||
}
|
||||
final now = DateTime.now();
|
||||
final steps = await health.getTotalStepsInInterval(
|
||||
kstMidnight(now),
|
||||
now,
|
||||
includeManualEntry: false,
|
||||
);
|
||||
if (steps == null || (Platform.isIOS && steps == 0)) return null;
|
||||
return steps;
|
||||
} catch (_) {
|
||||
_healthAuthorized = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 오늘 걸은 수 — **하루 단위 기준선**.
|
||||
///
|
||||
/// 걸음 센서가 주는 값은 [deviceStepCount] 설명대로 **부팅 이후 누적**이다. 정책 원장
|
||||
|
||||
Reference in New Issue
Block a user