main.dart 4.67 KB
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/date_symbol_data_local.dart';

import 'app/bootstrap/app_bootstrap.dart';
import 'app/double_feel_app.dart';
import 'core/services/raw_data_service/health_raw_data_core_service.dart';
import 'core/theme/app_theme.dart';
import 'data/local/user_preferences_storage.dart';

Future<void> main() async {
  runZonedGuarded<Future<Null>>(
    () async {
      WidgetsFlutterBinding.ensureInitialized();
      FlutterError.onError = (errorDetails) {};
      PlatformDispatcher.instance.onError = (error, stack) {
        return true;
      };

      SystemChrome.setSystemUIOverlayStyle(AppTheme.systemUiOverlayStyle);

      await AppBootstrap.init();
      await initializeDateFormatting();
      runApp(const DoubleFeelApp());
    },
    (error, stackTrace) async {
      if (kDebugMode) {}
    },
  );
}

@pragma('vm:entry-point')
Future<void> healthBackgroundMain() async {
  WidgetsFlutterBinding.ensureInitialized();
  const channel = MethodChannel('doublefeel_flutter_health_background_channel');

  await _healthBackgroundLog(channel, 'entry.start');
  try {
    await _healthBackgroundLog(channel, 'bootstrap.start');
    await AppBootstrap.init();
    await _healthBackgroundLog(channel, 'bootstrap.success');
  } catch (error, stackTrace) {
    await _healthBackgroundLog(
      channel,
      'bootstrap.failed error=$error stack=$stackTrace',
    );
    rethrow;
  }

  channel.setMethodCallHandler((call) async {
    switch (call.method) {
      case 'runPendingHealthTasks':
        await _runPendingHealthTasks(channel, call.arguments);
        return true;
      default:
        return null;
    }
  });

  await channel.invokeMethod('healthBackgroundTaskReady', {
    'timestamp': DateTime.now().millisecondsSinceEpoch ~/ 1000,
  });
  await _healthBackgroundLog(channel, 'entry.readySent');
}

Future<void> _runPendingHealthTasks(
  MethodChannel channel,
  Object? arguments,
) async {
  final tasks = _backgroundHealthTasksFromArguments(arguments);
  await _healthBackgroundLog(
    channel,
    'tasks.received count=${tasks.length} ids=${tasks.map((e) => e.taskId).join(',')}',
  );
  if (tasks.isEmpty) return;

  for (final task in tasks) {
    await channel.invokeMethod('healthBackgroundTaskReceived', {
      'taskId': task.taskId,
    });
  }

  final userPrefs = Get.find<UserPreferencesStorage>();
  final userId = userPrefs.preferences.value.meUserInfo?.id ?? 0;
  final token = userPrefs.accessToken;
  await _healthBackgroundLog(
    channel,
    'login.check userId=$userId hasToken=${token.isNotEmpty}',
  );
  if (userId <= 0 || token.isEmpty) {
    for (final task in tasks) {
      await channel.invokeMethod('healthBackgroundTaskFinished', {
        'taskId': task.taskId,
        'success': false,
        'retryable': true,
        'error': 'missingLogin',
      });
    }
    return;
  }

  try {
    await _healthBackgroundLog(channel, 'calculate.start');
    await Get.find<HealthRawDataCoreService>().startCoreCaculate();
    await _healthBackgroundLog(channel, 'calculate.success');
    for (final task in tasks) {
      await channel.invokeMethod('healthBackgroundTaskFinished', {
        'taskId': task.taskId,
        'success': true,
      });
    }
  } catch (error) {
    await _healthBackgroundLog(channel, 'calculate.failed error=$error');
    for (final task in tasks) {
      await channel.invokeMethod('healthBackgroundTaskFinished', {
        'taskId': task.taskId,
        'success': false,
        'retryable': true,
        'error': error.toString(),
      });
    }
  }
}

Future<void> _healthBackgroundLog(MethodChannel channel, String message) async {
  try {
    await channel.invokeMethod('healthBackgroundLog', {
      'message': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch ~/ 1000,
    });
  } catch (_) {}
}

List<_BackgroundHealthTask> _backgroundHealthTasksFromArguments(
  Object? arguments,
) {
  final rawTasksValue =
      arguments is Map<Object?, Object?> ? arguments['tasks'] : null;
  final rawTasks =
      rawTasksValue is List ? rawTasksValue.cast<Object?>() : const <Object?>[];
  return rawTasks
      .whereType<Map<Object?, Object?>>()
      .map(_BackgroundHealthTask.fromMap)
      .whereType<_BackgroundHealthTask>()
      .toList(growable: false);
}

class _BackgroundHealthTask {
  const _BackgroundHealthTask({required this.taskId});

  final String taskId;

  static _BackgroundHealthTask? fromMap(Map<Object?, Object?> map) {
    final taskId = map['taskId'];
    if (taskId is! String || taskId.isEmpty) return null;
    return _BackgroundHealthTask(taskId: taskId);
  }
}