Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M3-107 만보기 인터페이스 구현 (ANDROID) #7

Merged
merged 16 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
}

kotlinOptions {
Expand All @@ -49,6 +51,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}

buildTypes {
Expand All @@ -64,6 +67,8 @@ flutter {
source '../..'
}

dependencies {}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
}

apply from: project(':flutter_config').projectDir.getPath() + "/dotenv.gradle"
5 changes: 5 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />

<application
android:label="ground_flip"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="dataSync|remoteMessaging" android:exported="false" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/GOOGLE_MAP_API_KEY" />
Expand Down
30 changes: 28 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ PODS:
- Flutter (1.0.0)
- flutter_config (0.0.1):
- Flutter
- flutter_foreground_task (0.0.1):
- Flutter
- google_maps_flutter_ios (0.0.1):
- Flutter
- GoogleMaps (< 9.0, >= 8.4)
- GoogleMaps (< 10.0, >= 8.4)
- GoogleMaps (8.4.0):
- GoogleMaps/Maps (= 8.4.0)
- GoogleMaps/Base (8.4.0)
Expand All @@ -16,17 +18,29 @@ PODS:
- Flutter
- location (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- pedometer (0.0.1):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_config (from `.symlinks/plugins/flutter_config/ios`)
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
- google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
- health (from `.symlinks/plugins/health/ios`)
- location (from `.symlinks/plugins/location/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- pedometer (from `.symlinks/plugins/pedometer/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)

SPEC REPOS:
trunk:
Expand All @@ -39,24 +53,36 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_config:
:path: ".symlinks/plugins/flutter_config/ios"
flutter_foreground_task:
:path: ".symlinks/plugins/flutter_foreground_task/ios"
google_maps_flutter_ios:
:path: ".symlinks/plugins/google_maps_flutter_ios/ios"
health:
:path: ".symlinks/plugins/health/ios"
location:
:path: ".symlinks/plugins/location/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
pedometer:
:path: ".symlinks/plugins/pedometer/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"

SPEC CHECKSUMS:
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_config: f48f0d47a284f1791aacce2687eabb3309ba7a41
google_maps_flutter_ios: c454f18e0e22df6ac0e9f2a4df340858f5a3680c
flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817
google_maps_flutter_ios: 5bc2be60ad012e79b182ce0fb0ef5030a50fb03e
GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
health: 5a380c0f6c4f619535845992993964293962e99e
location: d5cf8598915965547c3f36761ae9cc4f4e87d22e
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
pedometer: 381969883680ade42559782cc41a3bbd453d8234
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78

PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

Expand Down
1 change: 0 additions & 1 deletion lib/controllers/search_group_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class GroupSearchController extends GetxController {

@override
void onClose() {
searchFocusnode.dispose();
searchFocusnode.dispose();
super.onClose();
}
Expand Down
7 changes: 6 additions & 1 deletion lib/controllers/walking_controller.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';

import 'package:get/get.dart';

Expand All @@ -25,8 +26,12 @@ class WalkingController extends GetxController {
walkingService = WalkingServiceFactory.getWalkingService();
_initializeWeeklySteps();
_initializeCurrentStep();
_initializeUpdateTimer();
}

Timer.periodic(Duration(seconds: 30), (timer) {
void _initializeUpdateTimer() {
int updateInterval = Platform.isIOS ? 60 : 1;
Timer.periodic(Duration(seconds: updateInterval), (timer) {
updateCurrentStep();
});
}
Expand Down
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';

import 'screens/main_screen.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
await GetStorage.init();
runApp(const MyApp());
}

Expand Down
4 changes: 3 additions & 1 deletion lib/screens/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:get/get.dart';
import '../controllers/navigation_controller.dart';
import '../widgets/common/naviagtion_bar.dart';



class MainScreen extends StatelessWidget {
const MainScreen({super.key});

Expand All @@ -13,7 +15,7 @@ class MainScreen extends StatelessWidget {
Get.put(NavigationController());

return Scaffold(
appBar: PreferredSize(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(() => navigationController.getCurrentAppBar()),
),
Expand Down
2 changes: 2 additions & 0 deletions lib/screens/search_group.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@


51 changes: 49 additions & 2 deletions lib/service/android_walking_service.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import 'dart:async';
import 'dart:isolate';

import 'package:flutter/cupertino.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';

import '../utils/android_notification.dart';
import '../utils/walking_service.dart';

//ToDo : 안드로이드 서비스 실제로 구현하기
class AndroidWalkingService implements WalkingService {
ReceivePort? _receivePort;
int currentSteps = 0;

static final AndroidWalkingService _instance =
AndroidWalkingService._internal();

AndroidWalkingService._internal() {
_initForegroundWalkingTask();
}

factory AndroidWalkingService() {
return _instance;
}

@override
Future<int> getCurrentStep() {
return Future.value(4251);
return Future.value(currentSteps);
}

@override
Expand All @@ -14,4 +34,31 @@ class AndroidWalkingService implements WalkingService {
) {
return Future.value([1500, 2500, 3500, 4500, 5500, 6500, 7500]);
}

Future<void> _initForegroundWalkingTask() async {
initForegroundTask();
final newReceivePort = FlutterForegroundTask.receivePort;
_registerReceivePort(newReceivePort);
}

bool _registerReceivePort(ReceivePort? newReceivePort) {
if (newReceivePort == null) {
return false;
}

_closeReceivePort();

_receivePort = newReceivePort;
_receivePort?.listen((data) {
currentSteps = data;
debugPrint('current walk: $data');
});

return _receivePort != null;
}

void _closeReceivePort() {
_receivePort?.close();
_receivePort = null;
}
}
138 changes: 138 additions & 0 deletions lib/utils/android_notification.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import 'dart:async';
import 'dart:isolate';

import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get_storage/get_storage.dart';
import 'package:pedometer/pedometer.dart';

Future<void> initForegroundTask() async {
if (!await FlutterForegroundTask.isRunningService) {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'ground-flip',
channelName: 'ground-flip Notification',
channelDescription:
'This notification appears when the ground-flip is running.',
channelImportance: NotificationChannelImportance.LOW,
priority: NotificationPriority.LOW,
iconData: const NotificationIconData(
resType: ResourceType.mipmap,
resPrefix: ResourcePrefix.ic,
name: 'launcher',
),
isSticky: true,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: true,
playSound: false,
),
foregroundTaskOptions: const ForegroundTaskOptions(
interval: 1000,
isOnceEvent: false,
autoRunOnBoot: true,
allowWakeLock: true,
allowWifiLock: true,
),
);
}

FlutterForegroundTask.startService(
notificationTitle: "걸음수",
notificationText: "0",
callback: startCallback,
);
}

@pragma('vm:entry-point')
void startCallback() {
FlutterForegroundTask.setTaskHandler(AndroidWalkingHandler());
}

class AndroidWalkingHandler extends TaskHandler {
SendPort? _sendPort;
late Stream<StepCount> _stepCountStream;
final GetStorage _localStorage = GetStorage();
static String todayStepKey = 'currentSteps';
static String lastSavedStepKey = 'lastSteps';
Timer? _midnightTimer;
int currentSteps = 0;

@override
void onStart(DateTime timestamp, SendPort? sendPort) async {
_sendPort = sendPort;
currentSteps = _localStorage.read(todayStepKey) ?? 0;
_stepCountStream = Pedometer.stepCountStream.asBroadcastStream();
_stepCountStream.listen(updateStep).onError(onStepCountError);
_initializeMidnightReset();
}

void onStepCountError(error) {
currentSteps = 0;
}

void _initializeMidnightReset() {
DateTime now = DateTime.now();
DateTime midnight = DateTime(now.year, now.month, now.day + 1);
Duration timeUntilMidnight = midnight.difference(now);
_midnightTimer?.cancel();
_midnightTimer = Timer(timeUntilMidnight, () {
_resetStepsAtMidnight();
_midnightTimer?.cancel();
_midnightTimer = Timer.periodic(Duration(days: 1), (timer) {
_resetStepsAtMidnight();
});
});
}

void _resetStepsAtMidnight() {
// TODO : 서버에 걸음수 저장하는 로직 추가
currentSteps = 0;
_localStorage.write(todayStepKey, 0);

FlutterForegroundTask.updateService(
notificationTitle: "걸음수",
notificationText: currentSteps.toString(),
);
_sendPort?.send(currentSteps);
}

updateStep(StepCount event) async {
int currentTotalStep = event.steps;

int? lastSavedStepCount = _localStorage.read(lastSavedStepKey);
int todaySteps = _localStorage.read(todayStepKey) ?? 0;

if (lastSavedStepCount == null) {
_localStorage.write(lastSavedStepKey, currentTotalStep);
lastSavedStepCount = currentTotalStep;
}

if (currentTotalStep < lastSavedStepCount) {
todaySteps += currentTotalStep;
lastSavedStepCount = currentTotalStep;
_localStorage.write(lastSavedStepKey, currentTotalStep);
} else {
int deltaSteps = currentTotalStep - lastSavedStepCount;
todaySteps += deltaSteps;
lastSavedStepCount = currentTotalStep;
}

_localStorage.write(todayStepKey, todaySteps);
_localStorage.write(lastSavedStepKey, currentTotalStep);
currentSteps = todaySteps;

FlutterForegroundTask.updateService(
notificationTitle: "걸음수",
notificationText: currentSteps.toString(),
);
_sendPort?.send(currentSteps);
}

@override
void onRepeatEvent(DateTime timestamp, SendPort? sendPort) async {
sendPort?.send(currentSteps);
}

@override
void onDestroy(DateTime timestamp, SendPort? sendPort) async {}
}
Loading