diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f5f664..a85f1ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ on: env: FLUTTER_VERSION: "3" - JAVA_VERSION: "11" + JAVA_VERSION: "17" jobs: build-android: diff --git a/android/app/build.gradle b/android/app/build.gradle index 250dc4e..07071aa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -50,7 +50,7 @@ apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { @@ -60,6 +60,7 @@ android { kotlinOptions { jvmTarget = '1.8' + languageVersion = '1.8' } sourceSets { @@ -72,7 +73,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 21 // flutter.minSdkVersion - targetSdkVersion 33 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8a76fc3..1743574 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ + + diff --git a/android/build.gradle b/android/build.gradle index 9f980fc..d817c2a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:8.1.0' classpath 'com.google.gms:google-services:4.3.13' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index cc5527d..fb5eb59 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip diff --git a/lib/views/taxiView.dart b/lib/views/taxiView.dart index b5e1327..47af588 100644 --- a/lib/views/taxiView.dart +++ b/lib/views/taxiView.dart @@ -35,6 +35,17 @@ class TaxiView extends HookWidget { FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + //TODO: Remove this on production + Future _requestLocationPermission() async { + var status = await Permission.locationWhenInUse.status; + if (status.isDenied) { + status = await Permission.locationWhenInUse.request(); + } + if (!status.isGranted) { + Fluttertoast.showToast(msg: "위치 권한이 거부되었습니다."); + } + } + @override Widget build(BuildContext context) { String address = RemoteConfigController().frontUrl; @@ -70,6 +81,12 @@ class TaxiView extends HookWidget { devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + //TODO: Remove this on production + useEffect(() { + _requestLocationPermission(); + return; + }, []); + useEffect(() { if (isTimerUp.value) { FcmToken().init().then((value) { @@ -171,7 +188,7 @@ class TaxiView extends HookWidget { LoadCount.value += 1; } else { final _appLinks = AppLinks(); - final Uri? uri = await _appLinks.getInitialAppLink(); + final Uri? uri = await _appLinks.getInitialLink(); if (uri != null) { final PendingDynamicLinkData? appLinkData = await FirebaseDynamicLinks.instance.getDynamicLink(uri); @@ -504,332 +521,358 @@ class TaxiView extends HookWidget { _goBack(context, backCount, isAuthLogin, _controller.value), child: Scaffold( body: InAppWebView( - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - useShouldOverrideUrlLoading: true, - applicationNameForUserAgent: "taxi-app-webview/" + - (Platform.isAndroid ? "android" : "ios"), - resourceCustomSchemes: [ - 'intent', - 'supertoss', - 'uber', - 'tmoneyonda', - 'kakaotalk', - 'kakaot' - ]), - android: AndroidInAppWebViewOptions( - useHybridComposition: true, - overScrollMode: - AndroidOverScrollMode.OVER_SCROLL_NEVER), - ios: IOSInAppWebViewOptions(disallowOverScroll: true)), - // initialUrlRequest: URLRequest(url: Uri.parse(address)), - shouldOverrideUrlLoading: (controller, navigationAction) async { - var newHeaders = Map.from( - navigationAction.request.headers ?? {}); - if (Platform.isAndroid && - !newHeaders.containsKey("Referer") && - navigationAction.request.url.toString() != - 'about:blank' && - (navigationAction.request.url?.origin == - Uri.parse(address).origin || - navigationAction.request.url?.origin == - Uri.parse(RemoteConfigController().backUrl) - .origin)) { - newHeaders['Referer'] = - navigationAction.request.url.toString(); - newHeaders['Origin'] = RemoteConfigController().frontUrl; - var newRequest = navigationAction.request; - newRequest.headers = newHeaders; - await controller.loadUrl(urlRequest: newRequest); - - return NavigationActionPolicy.CANCEL; - } + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + useShouldOverrideUrlLoading: true, + applicationNameForUserAgent: "taxi-app-webview/" + + (Platform.isAndroid ? "android" : "ios"), + resourceCustomSchemes: [ + 'intent', + 'supertoss', + 'uber', + 'tmoneyonda', + 'kakaotalk', + 'kakaot' + ]), + android: AndroidInAppWebViewOptions( + useHybridComposition: true, + overScrollMode: AndroidOverScrollMode.OVER_SCROLL_NEVER, + geolocationEnabled: true, + ), + ios: IOSInAppWebViewOptions( + disallowOverScroll: true, + allowsInlineMediaPlayback: true, + )), + // initialUrlRequest: URLRequest(url: Uri.parse(address)), + shouldOverrideUrlLoading: (controller, navigationAction) async { + var newHeaders = Map.from( + navigationAction.request.headers ?? {}); + if (Platform.isAndroid && + !newHeaders.containsKey("Referer") && + navigationAction.request.url.toString() != 'about:blank' && + (navigationAction.request.url?.origin == + Uri.parse(address).origin || + navigationAction.request.url?.origin == + Uri.parse(RemoteConfigController().backUrl) + .origin)) { + newHeaders['Referer'] = + navigationAction.request.url.toString(); + newHeaders['Origin'] = RemoteConfigController().frontUrl; + var newRequest = navigationAction.request; + newRequest.headers = newHeaders; + await controller.loadUrl(urlRequest: newRequest); + + return NavigationActionPolicy.CANCEL; + } + + return NavigationActionPolicy.ALLOW; + }, + onWebViewCreated: (InAppWebViewController webcontroller) async { + _controller.value = webcontroller; + _controller.value?.addJavaScriptHandler( + handlerName: "auth_update", + callback: (arguments) async { + // 로그인 해제 시 로그인 State 변경 + if (arguments == [{}]) { + isLogin.value = false; + return; + } + // 로그인 성공 시 / 기존 토큰 삭제 후 새로운 토큰 저장 + if (!isAuthLogin.value) { + if (arguments[0]['accessToken'] != null && + arguments[0]['refreshToken'] != null) { + await Token().deleteAll(); + await Token().setAccessToken( + accessToken: arguments[0]['accessToken']); + await Token().setRefreshToken( + refreshToken: arguments[0]['refreshToken']); + await FcmToken() + .registerToken(arguments[0]['accessToken']); + isAuthLogin.value = true; + } + } + return; + }, + ); - return NavigationActionPolicy.ALLOW; - }, - onWebViewCreated: (InAppWebViewController webcontroller) async { - _controller.value = webcontroller; - _controller.value?.addJavaScriptHandler( - handlerName: "auth_update", - callback: (arguments) async { - // 로그인 해제 시 로그인 State 변경 - if (arguments == [{}]) { + _controller.value?.addJavaScriptHandler( + handlerName: "auth_logout", + callback: (args) async { + try { + await FcmToken().removeToken(Token().getAccessToken()); + await Token().deleteAll(); + await _cookieManager.deleteAllCookies(); isLogin.value = false; - return; + isAuthLogin.value = false; + await _controller.value?.loadUrl( + urlRequest: URLRequest( + url: Uri.parse(RemoteConfigController() + .frontUrl + .toString()))); + } catch (e) { + // TODO + Fluttertoast.showToast( + msg: "서버와의 연결에 실패했습니다.", + toastLength: Toast.LENGTH_SHORT, + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + isAuthLogin.value = false; } - // 로그인 성공 시 / 기존 토큰 삭제 후 새로운 토큰 저장 - if (!isAuthLogin.value) { - if (arguments[0]['accessToken'] != null && - arguments[0]['refreshToken'] != null) { - await Token().deleteAll(); - await Token().setAccessToken( - accessToken: arguments[0]['accessToken']); - await Token().setRefreshToken( - refreshToken: arguments[0]['refreshToken']); - await FcmToken() - .registerToken(arguments[0]['accessToken']); - isAuthLogin.value = true; - } + }); + + _controller.value?.addJavaScriptHandler( + handlerName: "try_notification", + callback: (args) async { + if (await Permission.notification.isGranted) { + return true; + } else { + openAppSettings(); + Fluttertoast.showToast( + msg: "알림 권한을 허용해주세요.", + toastLength: Toast.LENGTH_SHORT, + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + return false; } - return; - }, - ); - - _controller.value?.addJavaScriptHandler( - handlerName: "auth_logout", - callback: (args) async { - try { - await FcmToken() - .removeToken(Token().getAccessToken()); - await Token().deleteAll(); - await _cookieManager.deleteAllCookies(); - isLogin.value = false; - isAuthLogin.value = false; - await _controller.value?.loadUrl( - urlRequest: URLRequest( - url: Uri.parse(RemoteConfigController() - .frontUrl - .toString()))); - } catch (e) { - // TODO - Fluttertoast.showToast( - msg: "서버와의 연결에 실패했습니다.", - toastLength: Toast.LENGTH_SHORT, - textColor: toastTextColor, - backgroundColor: toastBackgroundColor); - isAuthLogin.value = false; - } - }); - - _controller.value?.addJavaScriptHandler( - handlerName: "try_notification", - callback: (args) async { - if (await Permission.notification.isGranted) { - return true; - } else { - openAppSettings(); - Fluttertoast.showToast( - msg: "알림 권한을 허용해주세요.", - toastLength: Toast.LENGTH_SHORT, - textColor: toastTextColor, - backgroundColor: toastBackgroundColor); - return false; - } - }); + }); - _controller.value?.addJavaScriptHandler( - handlerName: "clipboard_copy", - callback: (args) async { - if (Platform.isAndroid) { - await Clipboard.setData(ClipboardData(text: args[0])); - } - }); - - // Web -> App - _controller.value?.addJavaScriptHandler( - handlerName: "popup_inAppNotification", - callback: (args) async { - try { - int types = 0; - switch (args[0]['type'].toString()) { - case "default": - types = 0; - break; - case "chat": - types = 1; - break; - } - createOverlayNotification( - title: args[0]['title'].toString(), - subTitle: args[0]['subtitle'].toString(), - content: args[0]['content'].toString(), - button: (args[0].containsKey("button")) - ? { - args[0]['button']['text'].toString(): - args[0]['button']['path'].toString() - } - : {"": ""}, - type: types, - imageUrl: (args[0]['type'].toString() == - "default") - ? Uri.parse(args[0]['imageUrl'].toString()) - : Uri.parse( - args[0]['profileUrl'].toString())); - } on Exception catch (e) { - Fluttertoast.showToast( - msg: "인앱 알림 로드에 실패하였습니다.", - toastLength: Toast.LENGTH_SHORT, - textColor: toastTextColor, - backgroundColor: toastBackgroundColor); - return false; - } + _controller.value?.addJavaScriptHandler( + handlerName: "try_location", + callback: (args) async { + if (await Permission.locationWhenInUse.isGranted) { return true; - }); + } else { + openAppSettings(); + Fluttertoast.showToast( + msg: "위치 권한을 허용해주세요.", + toastLength: Toast.LENGTH_SHORT, + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + return false; + } + }); - _controller.value?.addJavaScriptHandler( - handlerName: "popup_instagram_story_share", - callback: (args) async { - if (args[0] == {}) { - return false; - } - try { - final Dio _dio = Dio(); - final backgroundResponse = await _dio.get( - args[0]['backgroundLayerUrl'], - options: - Options(responseType: ResponseType.bytes)); - final stickerResponse = await _dio.get( - args[0]['stickerLayerUrl'], - options: - Options(responseType: ResponseType.bytes)); - final backgroundFile = await File( - (await getTemporaryDirectory()).path + - "/background.png") - .create(recursive: true); - final stickerFile = await File( - (await getTemporaryDirectory()).path + - "/sticker.png") - .create(recursive: true); - await backgroundFile - .writeAsBytes(backgroundResponse.data); - await stickerFile.writeAsBytes(stickerResponse.data); - - await SocialShare.shareInstagramStory( - appId: dotenv.get("FACEBOOK_APPID"), - imagePath: stickerFile.path, - backgroundResourcePath: backgroundFile.path); - return true; - } catch (e) { - Fluttertoast.showToast( - msg: "인스타그램 스토리 공유에 실패했습니다.", - toastLength: Toast.LENGTH_SHORT, - textColor: toastTextColor, - backgroundColor: toastBackgroundColor); - return false; + _controller.value?.addJavaScriptHandler( + handlerName: "clipboard_copy", + callback: (args) async { + if (Platform.isAndroid) { + await Clipboard.setData(ClipboardData(text: args[0])); + } + }); + + // Web -> App + _controller.value?.addJavaScriptHandler( + handlerName: "popup_inAppNotification", + callback: (args) async { + try { + int types = 0; + switch (args[0]['type'].toString()) { + case "default": + types = 0; + break; + case "chat": + types = 1; + break; } - }); - }, - onLoadStart: (controller, uri) async { - if (isFcmInit.value && - isLogin.value && - sessionToken.value != '' && - uri?.origin == Uri.parse(address).origin && - (await _cookieManager.getCookie( - url: Uri.parse( - RemoteConfigController().backUrl), - name: "connect.sid")) - ?.value != - sessionToken.value) { - try { - await _controller.value?.stopLoading(); - await _cookieManager.deleteCookie( - url: Uri.parse(RemoteConfigController().backUrl), - name: "connect.sid"); - await _cookieManager.setCookie( - url: Uri.parse(RemoteConfigController().backUrl), - name: "connect.sid", - value: sessionToken.value, - ); - await _controller.value?.reload(); - } catch (e) { - // TODO : handle error - Fluttertoast.showToast( - msg: "서버와의 연결에 실패했습니다.", - toastLength: Toast.LENGTH_SHORT, - textColor: toastTextColor, - backgroundColor: toastBackgroundColor); - isAuthLogin.value = false; - } - } - }, - onLoadResourceCustomScheme: (controller, url) async { - if (!['intent'].contains(url.scheme)) { - await controller.stopLoading(); - if (await canLaunchUrlString(url.toString())) { - await launchUrlString(url.toString(), - mode: LaunchMode.externalApplication); - return; - } - switch (url.scheme) { - case 'supertoss': - OpenStore.instance.open( - androidAppBundleId: "viva.republica.toss", - appStoreId: "839333328"); - break; - case 'uber': - OpenStore.instance.open( - androidAppBundleId: "com.ubercab", - appStoreId: "368677368"); - break; - case 'tmoneyonda': - OpenStore.instance.open( - androidAppBundleId: "kr.co.orangetaxi.passenger", - appStoreId: "1489918157"); - break; - case 'kakaotalk': //카카오페이 결제시 - OpenStore.instance.open( - androidAppBundleId: "com.kakao.talk", - appStoreId: "362057947"); - break; - case 'kakaot': - OpenStore.instance.open( - androidAppBundleId: "com.kakao.taxi", - appStoreId: "981110422"); - break; - default: - await Fluttertoast.showToast( - msg: "해당 앱을 실행할 수 없습니다.", + createOverlayNotification( + title: args[0]['title'].toString(), + subTitle: args[0]['subtitle'].toString(), + content: args[0]['content'].toString(), + button: (args[0].containsKey("button")) + ? { + args[0]['button']['text'].toString(): + args[0]['button']['path'].toString() + } + : {"": ""}, + type: types, + imageUrl: (args[0]['type'].toString() == "default") + ? Uri.parse(args[0]['imageUrl'].toString()) + : Uri.parse(args[0]['profileUrl'].toString())); + } on Exception catch (e) { + Fluttertoast.showToast( + msg: "인앱 알림 로드에 실패하였습니다.", toastLength: Toast.LENGTH_SHORT, - textColor: Colors.black, - backgroundColor: Colors.white); - break; - } - return null; - } - if (Platform.isAndroid) { - if (url.scheme == 'intent') { + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + return false; + } + return true; + }); + + _controller.value?.addJavaScriptHandler( + handlerName: "popup_instagram_story_share", + callback: (args) async { + if (args[0] == {}) { + return false; + } try { - await controller.stopLoading(); - const MethodChannel channel = - MethodChannel('org.sparcs.taxi_app/taxi_only'); - final result = await channel.invokeMethod( - "launchURI", url.toString()); - if (result != null) { - await _controller.value?.loadUrl( - urlRequest: URLRequest(url: Uri.parse(result))); - } + final Dio _dio = Dio(); + final backgroundResponse = await _dio.get( + args[0]['backgroundLayerUrl'], + options: Options(responseType: ResponseType.bytes)); + final stickerResponse = await _dio.get( + args[0]['stickerLayerUrl'], + options: Options(responseType: ResponseType.bytes)); + final backgroundFile = await File( + (await getTemporaryDirectory()).path + + "/background.png") + .create(recursive: true); + final stickerFile = await File( + (await getTemporaryDirectory()).path + + "/sticker.png") + .create(recursive: true); + await backgroundFile + .writeAsBytes(backgroundResponse.data); + await stickerFile.writeAsBytes(stickerResponse.data); + + await SocialShare.shareInstagramStory( + appId: dotenv.get("FACEBOOK_APPID"), + imagePath: stickerFile.path, + backgroundResourcePath: backgroundFile.path); + return true; } catch (e) { - // TODO - await Fluttertoast.showToast( - msg: "카카오톡을 실행할 수 없습니다.", + Fluttertoast.showToast( + msg: "인스타그램 스토리 공유에 실패했습니다.", toastLength: Toast.LENGTH_SHORT, textColor: toastTextColor, backgroundColor: toastBackgroundColor); + return false; } - } - } - return null; - }, - onLoadError: (controller, url, code, message) { - // 될 때까지 리로드 - if (!isLoaded.value && LoadCount.value < 10) { - LoadCount.value++; - } else if (isServerError.value == false && - code != 102 && - code != -999) { + }); + }, + onLoadStart: (controller, uri) async { + if (isFcmInit.value && + isLogin.value && + sessionToken.value != '' && + uri?.origin == Uri.parse(address).origin && + (await _cookieManager.getCookie( + url: + Uri.parse(RemoteConfigController().backUrl), + name: "connect.sid")) + ?.value != + sessionToken.value) { + try { + await _controller.value?.stopLoading(); + await _cookieManager.deleteCookie( + url: Uri.parse(RemoteConfigController().backUrl), + name: "connect.sid"); + await _cookieManager.setCookie( + url: Uri.parse(RemoteConfigController().backUrl), + name: "connect.sid", + value: sessionToken.value, + ); + await _controller.value?.reload(); + } catch (e) { + // TODO : handle error Fluttertoast.showToast( msg: "서버와의 연결에 실패했습니다.", toastLength: Toast.LENGTH_SHORT, textColor: toastTextColor, backgroundColor: toastBackgroundColor); - isServerError.value = true; + isAuthLogin.value = false; } - }, - onLoadStop: (finish, uri) async { - if (!isServerError.value) { - isLoaded.value = true; + } + }, + onLoadResourceCustomScheme: (controller, url) async { + if (!['intent'].contains(url.scheme)) { + await controller.stopLoading(); + if (await canLaunchUrlString(url.toString())) { + await launchUrlString(url.toString(), + mode: LaunchMode.externalApplication); + return; + } + switch (url.scheme) { + case 'supertoss': + OpenStore.instance.open( + androidAppBundleId: "viva.republica.toss", + appStoreId: "839333328"); + break; + case 'uber': + OpenStore.instance.open( + androidAppBundleId: "com.ubercab", + appStoreId: "368677368"); + break; + case 'tmoneyonda': + OpenStore.instance.open( + androidAppBundleId: "kr.co.orangetaxi.passenger", + appStoreId: "1489918157"); + break; + case 'kakaotalk': //카카오페이 결제시 + OpenStore.instance.open( + androidAppBundleId: "com.kakao.talk", + appStoreId: "362057947"); + break; + case 'kakaot': + OpenStore.instance.open( + androidAppBundleId: "com.kakao.taxi", + appStoreId: "981110422"); + break; + default: + await Fluttertoast.showToast( + msg: "해당 앱을 실행할 수 없습니다.", + toastLength: Toast.LENGTH_SHORT, + textColor: Colors.black, + backgroundColor: Colors.white); + break; } - }), + return null; + } + if (Platform.isAndroid) { + if (url.scheme == 'intent') { + try { + await controller.stopLoading(); + const MethodChannel channel = + MethodChannel('org.sparcs.taxi_app/taxi_only'); + final result = await channel.invokeMethod( + "launchURI", url.toString()); + if (result != null) { + await _controller.value?.loadUrl( + urlRequest: URLRequest(url: Uri.parse(result))); + } + } catch (e) { + // TODO + await Fluttertoast.showToast( + msg: "카카오톡을 실행할 수 없습니다.", + toastLength: Toast.LENGTH_SHORT, + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + } + } + } + return null; + }, + onLoadError: (controller, url, code, message) { + // 될 때까지 리로드 + if (!isLoaded.value && LoadCount.value < 10) { + LoadCount.value++; + } else if (isServerError.value == false && + code != 102 && + code != -999) { + Fluttertoast.showToast( + msg: "서버와의 연결에 실패했습니다.", + toastLength: Toast.LENGTH_SHORT, + textColor: toastTextColor, + backgroundColor: toastBackgroundColor); + isServerError.value = true; + } + }, + onLoadStop: (finish, uri) async { + if (!isServerError.value) { + isLoaded.value = true; + } + }, + androidOnPermissionRequest: + (controller, origin, resources) async { + return PermissionRequestResponse( + resources: resources, + action: PermissionRequestResponseAction.GRANT); + }, + androidOnGeolocationPermissionsShowPrompt: + (InAppWebViewController controller, String origin) async { + return GeolocationPermissionShowPromptResponse( + origin: origin, allow: true, retain: true); + }, + ), )), isTimerUp.value && isLoaded.value && isFcmInit.value ? const Stack() diff --git a/pubspec.yaml b/pubspec.yaml index 98ceec1..5fe8549 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,9 +37,9 @@ dependencies: flutter_inappwebview: ^5.7.2 flutter_secure_storage: ^8.0.0 dio: ^5.0.0 - firebase_messaging: ^14.6.1 - flutter_local_notifications: ^14.0.0 - firebase_core: ^2.13.0 + firebase_messaging: ^15.1.2 + flutter_local_notifications: ^17.2.3 + firebase_core: ^3.5.0 flutter_web_auth: ^0.5.0 dio_cookie_manager: ^2.1.4 google_fonts: ^4.0.0 @@ -47,18 +47,19 @@ dependencies: cookie_jar: ^3.0.1 flutter_dotenv: ^5.0.2 package_info: ^2.0.2 - firebase_remote_config: ^3.0.14 + firebase_remote_config: ^5.1.2 open_store: ^0.5.0 permission_handler: ^10.2.0 - firebase_dynamic_links: ^5.1.1 - app_links: ^3.4.3 - firebase_crashlytics: ^3.3.1 + firebase_dynamic_links: ^6.0.7 + app_links: ^6.3.2 + firebase_crashlytics: ^4.1.2 url_launcher: ^6.1.14 social_share: ^2.3.1 path_provider: ^2.1.1 http: ^1.1.0 swipeable_tile: ^2.0.0+3 linear_timer: ^2.0.0 + geolocator: ^13.0.1 dev_dependencies: flutter_test: sdk: flutter @@ -68,7 +69,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec