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

Android inline ad performance #269

Closed
jjliu15 opened this issue Jun 8, 2021 · 106 comments
Closed

Android inline ad performance #269

jjliu15 opened this issue Jun 8, 2021 · 106 comments
Labels
bug Something isn't working e3-weeks Effort: < 4 weeks fixed Issue has been resolved and pull request linked p2-medium performance issue covers plugin performance platform-android Android applications specifically

Comments

@jjliu15
Copy link
Collaborator

jjliu15 commented Jun 8, 2021

There have been a number of complaints about performance of inline banner and native ads on Android:

As mentioned in #80 (comment), performance is noticeably worse on Android 9 and below. This is related to use of hybrid composition, which is a requirement due to technical constraints.

Currentworkarounds are to constrict inline ads to android 10 and above, or only to only use fixed position banner/native ads.

@jjliu15 jjliu15 added the bug Something isn't working label Jun 8, 2021
@svprdga
Copy link

svprdga commented Jun 8, 2021

Not show inline ads to android < 10 is a disaster from a business perspective, we are talking of thousands of users.

@jjliu15 does this same plugin support the fixed position banners you are talking about (just like the deprecated plugin did)?

@jjliu15
Copy link
Collaborator Author

jjliu15 commented Jun 8, 2021

Fixed position banners are supported, but the implementation is different than before. Now you need to align the flutter widget that displays the banner ad to a fixed position. You can see the example app for a reference

@svprdga
Copy link

svprdga commented Jun 9, 2021

Thanks @jjliu15

I have tried the solution proposed here and it works perfect. I don't know the implementation details that this user did but, but maybe here we have a possible solution to the problem.

@colbymaloy
Copy link

Sadly this doesnt do much for me. Using A stack with InteractiveViewer and a bottom aligned banner ad, my frame time is 1000ms on an IpadPro 12.9inch.

@blasten
Copy link

blasten commented Jul 21, 2021

We have added a flag to the Flutter master channel that helps improve the performance.

It's not a perfect fix since the Ad may be a few frames behind the Flutter frame, but depending on the layout you use, it could be useful.

Please try it out, and let us know if the performance is improved.

To use this flag, set PlatformViewsService.synchronizeToNativeViewHierarchy(false). For example:

import 'package:flutter/services.dart';

void main() {
   PlatformViewsService.synchronizeToNativeViewHierarchy(false);
   runApp(MyApp());
}

@sleepingkit
Copy link

We have added a flag to the Flutter master channel that helps improve the performance.

It's not a perfect fix since the Ad may be a few frames behind the Flutter frame, but depending on the layout you use, it could be useful.

Please try it out, and let us know if the performance is improved.

To use this flag, set PlatformViewsService.synchronizeToNativeViewHierarchy(false). For example:

import 'package:flutter/services.dart';

void main() {
   PlatformViewsService.synchronizeToNativeViewHierarchy(false);
   runApp(MyApp());
}

I am in master branch but didn't see this method.
The method 'synchronizeToNativeViewHierarchy' isn't defined for the type 'PlatformViewsService'.

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel master, 2.4.0-5.0.pre.140, on macOS 11.4 20F71 darwin-arm, locale zh-HK)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
✗ cmdline-tools component is missing
Run path/to/sdkmanager --install "cmdline-tools;latest"
See https://developer.android.com/studio/command-line for more details.
✗ Android license status unknown.
Run flutter doctor --android-licenses to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.57.1)
[✓] Connected device (3 available)

! Doctor found issues in 1 category.

@blasten
Copy link

blasten commented Jul 22, 2021

@svprdga
Copy link

svprdga commented Jul 22, 2021

Hi @blasten, I have performed some tests:

  • Device: Samsung Galaxy A3 (2017)
  • The tests consists in open an app with some widgets (and an inline add that renders in a few seconds) and then open and close a PopupMenuButton a few times
  • google_mobile_ads: 0.13.2
  • Running the app in profile mode

Stable branch

stable-branch

Master branch with synchronizeToNativeViewHierarchy to false

with-flag-true

I think that the performance is a little bit worse in the master branch with this flag.

I have also performed the same test with this fork:

fork

The author of this fork says here that he's using AndroidView, is there any reason why this solution is not applied to this plugin?

@blasten
Copy link

blasten commented Jul 23, 2021

@svprdga that doesn’t seem right. It can’t possibly be more expensive, since there’s less work.

We also enable thread merging, which is the other thing that reduces performance.

Normally in a Flutter app, there is a raster thread and a platform/Android main UI thread. When we show an Android view, we are unable to continue with this thread configuration due to the dependencies and constraints. For example, you cannot add a view to an Android app from a different thread, doing so is prohibited.

Would you be willing to share your app, and I can profile it further?

@naamapps
Copy link

@blasten Why is everyone ignoring this fork #80 (comment)?
It is obviously a good solution to the performance issues.
Please, give a reason to why not merge it to the package and also why every contributor is ignoring it.
Thanks

@svprdga
Copy link

svprdga commented Jul 23, 2021

@blasten I can't share the app, but I have recreated the issue in this sample project. (*) Remember to set your app ID in AndroidManifest

  • Device: Samsung Galaxy A3 (2017)
  • Open/close the PopupMenuButton in the top bar
  • google_mobile_ads: 0.13.2
  • Running the app in profile mode

Stable branch

stable-branch

Master branch

master-branch

I'd say that has improved a little bit, but far from being efficient. The UI I have created is extremely simple, with a more complex UI the performance is worse.

@blasten
Copy link

blasten commented Jul 23, 2021

@naamapps we aren't. We used that approach initially, but hit technical limitations - hybrid composition workarounds those limitations.

Stay tuned for future improvements.

@naamapps
Copy link

@blasten It's great to hear that the development is active and you're trying to make the package better.
Another question I had and I opened an issue on it #313.
Is there a reason this package uses native views (which are expensive and have a lot of performance issues) instead of actually using flutter widgets?

@svprdga
Copy link

svprdga commented Jul 24, 2021

@blasten I am right now using the fork in many apps, around 10k users combined, without any issue. I can't say the same regarding the official package (I had to quickly replace it once I realized how bad the performance was).
Can you elaborate more on which are these technical limitations?
Thank you.

@bdlukaa
Copy link

bdlukaa commented Jul 25, 2021

Can you elaborate more on which are these technical limitations?

I think the reason is because AndroidView (virtual display) performs the click programmatically, which isn't allowed by admob.

@svprdga
Copy link

svprdga commented Jul 27, 2021

Thanks for the clarification @bdlukaa .

If this is the case, then we have to decide between:

  • Comply with AdMob policy, use the official package, and provide a bad experience for those users who don't have the luck to have a powerful device; which will eventually lead to bad reviews, a drop in user acquisition, losing this mass of users and basically reduce the revenue income dramatically.
  • Don't comply with the policy, use another plugin to provide the expected smooth experience, and then being exposed to sanctions or account suspensions.

Perfect.

@atrope
Copy link
Contributor

atrope commented Jul 27, 2021

Only to add to the discussion,
In our app we restrict some models(via Remote Config) because of degraded performance.

Those days I Was also thinking how to provide a better experience for the user and then those ideas came:

1.- Inject the JS code inside a Webview. This worked, but had the same "problem" as the current implementation because inappwebview uses Android View as well - https://inappwebview.dev/docs/in-app-webview/basic-usage/

2 - Make a Dart solution fetching the ad from Admanager and firing all the needed callbacks (This sound the best solution but comes with caveats that we can't bear like no Viewability Tracking, no Open Bidding, no AdX etc) - https://support.google.com/admanager/answer/2623168?hl=en
Maybe the ads team can help us and we can remake this plugin 100% in dart(at least for banners?)

If anyone thinks on a better solutions we can try to implement so we can check performance-wise if it would be ok :)

@naamapps
Copy link

@atrope a 100% dart solution is what we need I think.
Webviews and native views are not performant in flutter, so making actual widgets and having the logic in dart is what will make the package complete.

@bdlukaa
Copy link

bdlukaa commented Jul 27, 2021

@atrope recently I've been thinking of creating a startup called "Flutter Ads" or something like that, that provides ads and has a full-dart implementation, but I lost interest on it. I'll leave this idea here in case of someone wants to work on it.

@atrope
Copy link
Contributor

atrope commented Jul 27, 2021

Hey guys I made here something so we can check performance for the loaded banners using somewhat native requests..

DO NOT USE THIS TO REQUEST ADS in your Test/Prod App, it has none of the requirements for it.

Be sure to have House Simple ads so it can fetch something as it does not work for dynamic allocation nor adx.

it uses flutter_html to parse the html returned by dfp and dio for request.
so include it in your project:

  dio: ^4.0.0
  flutter_html:
    git:
      url: https://github.com/Sub6Resources/flutter_html


import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';

class BannerTest extends StatefulWidget {
  const BannerTest({
    Key? key,
    required this.adUnit,
    required this.sizes,
  }) : super(key: key);
  final String adUnit;
  final List<String> sizes;

  @override
  State<BannerTest> createState() => _BannerTestState();
}

class _BannerTestState extends State<BannerTest> {
  String? data;
  @override
  void initState() {
    super.initState();
    final params = {
      'iu': widget.adUnit,
      'sz': widget.sizes.join('%7c'),
      'c': Random().nextInt(429497629),
      'tile': Random().nextInt(429497629)
    }.entries.map((e) => '${e.key}=${e.value}').toList().join('&');

    final url = 'https://securepubads.g.doubleclick.net/gampad/adx?$params';

    final headers = {
      'User-Agent':
          'Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.115 Mobile Safari/537.36',
    };
    Dio().get(url, options: Options(headers: headers)).then((value) {
      setState(() {
        data = value.data as String;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return data == null ? Container() : Html(data: data!);
  }
}

Call the banner anywhere with

BannerTest(
            adUnit: adUnit,
           sizes: const ['320x50', '320x100'],
         )

Send here the performance results :)

@jangruenwaldt
Copy link

Thanks for the clarification @bdlukaa .

If this is the case, then we have to decide between:

  • Comply with AdMob policy, use the official package, and provide a bad experience for those users who don't have the luck to have a powerful device; which will eventually lead to bad reviews, a drop in user acquisition, losing this mass of users and basically reduce the revenue income dramatically.
  • Don't comply with the policy, use another plugin to provide the expected smooth experience, and then being exposed to sanctions or account suspensions.

Perfect.

Wow, that sums it up so well. Ads are extremely important for devs. Please Flutter Team, put some focus on this!

This is quite a bottleneck for flutter applications. Many many people would really appreciate it. We can not realistically write this ad library ourselves.

@rocketman7

This comment was marked as off-topic.

@erperejildo

This comment was marked as off-topic.

@blasten
Copy link

blasten commented Mar 4, 2022

Hey everyone,

I've been working on improvement perf of Android native views.

Please try the latest changes, and share any findings:

Steps

  1. Switch to Flutter master channel
flutter channel master
flutter upgrade
  1. Download the changes required for google_mobile_ads
git clone -b tx --single-branch https://github.com/googleads/googleads-mobile-flutter.git
  1. Point the google_mobile_ads package to the location where you cloned the plugin
  google_mobile_ads:
    path: <path>/googleads-mobile-flutter/packages/google_mobile_ads
  1. Rebuild the app with any flutter command.

@blasten blasten reopened this Mar 4, 2022
@ilkanimal
Copy link

ilkanimal commented Mar 5, 2022

Hi @blasten,
With these last changes, banner placed in the ListView is getting impression before it appears on the screen.
This conflicts with Admob policy. (https://support.google.com/admob/answer/3269069?hl=en)
In the previous version, getting impression only when the first pixel of banner is seen on the screen.

By the way, at first glance I can say that the performance has noticeably improved. I'll be doing other production-level tests, but these improvements seem to live up to expectations.

Also, there is a scrolling issue with the WebView when banner and WebView are in the same screen. WebView's scrolling is not working. (WebView has custom GestureDetector) If you want to hear more, I can share details.

Example code for the impression issue
class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: 15,
        itemBuilder: (context, index) {
          if (index != 0 && index % 5 == 0) {
            return const _BannerAd();
          }

          return Container(
            height: 200,
            color: Color((Random().nextDouble() * 0xFFFFFF).toInt())
                .withOpacity(1),
          );
        },
      ),
    );
  }
}

class _BannerAd extends StatefulWidget {
  const _BannerAd({Key? key}) : super(key: key);

  @override
  _BannerAdState createState() => _BannerAdState();
}

class _BannerAdState extends State<_BannerAd>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  late BannerAd myBanner;

  @override
  void initState() {
    super.initState();
    myBanner = BannerAd(
      adUnitId: BannerAd.testAdUnitId,
      size: AdSize.banner,
      request: const AdRequest(),
      listener: BannerAdListener(
        onAdLoaded: (ad) => print('Ad loaded.'),
        onAdFailedToLoad: (ad, error) {
          ad.dispose();
          print('Ad failed to load .');
        },
        onAdOpened: (ad) => print('Ad opened.'),
        onAdClosed: (ad) => print('Ad closed.'),
        onAdImpression: (ad) => print('Ad impression.'),
      ),
    )..load();
  }

  @override
  void dispose() {
    myBanner.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return Container(
      width: myBanner.size.width.toDouble(),
      height: myBanner.size.height.toDouble(),
      margin: const EdgeInsets.all(20),
      alignment: Alignment.center,
      child: AdWidget(ad: myBanner),
    );
  }
}

@cmasdf
Copy link

cmasdf commented Mar 5, 2022

Hi @blasten, thank you very much for your renewed efforts to improve the performance - as stated before, performance has already been improved noticeably.

Unfortunately some native ads are not showing up on older Android versions anymore even if they are loaded according to the callback. I am not sure if @ilkanimal is talking about the same problem.
I am receiving the onLoaded callback, but it looks like the texture is not loaded correctly, the widget just stays empty. The widgets are working correctly with the latest stable version of the plugin.
It works perfect on Android 12 emulator, but shows the described behaviour with emulated and native Android 10. Due to the bug described in #158, i could not test it on Android 11. Is there any known solution or a chance that you might be able to have a look into this?

@blasten
Copy link

blasten commented Mar 5, 2022

@cmasdf yes. We are aware of some issues. flutter/flutter#98722
One fix is already in progress: flutter/engine#31698

@blasten
Copy link

blasten commented Mar 7, 2022

With these last changes, banner placed in the ListView is getting impression before it appears on the screen.
This conflicts with Admob policy. (https://support.google.com/admob/answer/3269069?hl=en)
In the previous version, getting impression only when the first pixel of banner is seen on the screen.

That's good feedback. The current implementation triggers the create event when the ad is in the viewport.

@ilkanimal

@medst
Copy link

medst commented Mar 13, 2022

Fatal Exception: java.lang.LinkageError

Fatal Exception: java.lang.LinkageError: Method java.lang.Object com.google.android.gms.internal.ads.to3.zzb() overrides final method in class Lcom/google/android/gms/internal/ads/lo3; (declaration of 'com.google.android.gms.internal.ads.to3' appears in base.apk)
       at com.google.android.gms.internal.ads.zzcpq.zza(zzcpq.java:44)
       at com.google.android.gms.internal.ads.zzevk.zza(zzevk.java:365)
       at com.google.android.gms.internal.ads.zzeke.zzN(zzeke.java:58)
       at com.google.android.gms.internal.ads.zzeke.zze(zzeke.java:6)
       at com.google.android.gms.internal.ads.zzbhd.zzg(zzbhd.java:203)
       at com.google.android.gms.ads.BaseAdView.loadAd(BaseAdView.java:6)
       at io.flutter.plugins.googlemobileads.FlutterBannerAd.load(FlutterBannerAd.java:60)
       at io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.onMethodCall(GoogleMobileAdsPlugin.java:883)
       at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:17)
       at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:18)
       at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0(DartMessenger.java:20)
       at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java)
       at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(-.java:12)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6740)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

2022-03-13

@blasten
Copy link

blasten commented Mar 15, 2022

@jjliu15 does the issue above look familiar?

@blasten
Copy link

blasten commented Mar 16, 2022

This issue was fixed by flutter/flutter#100091.

Other than #532, no more changes are required in the googleads plugin.

@maheshj01
Copy link
Collaborator

Fatal Exception: java.lang.LinkageError

@medst that issue is being tracked here #471

@maheshj01 maheshj01 added the fixed Issue has been resolved and pull request linked label Mar 22, 2022
@jeromeDms
Copy link

Switching to flutter beta channel largely improved performances in my case.

@nero-angela
Copy link

Similar performance issues occur in ios, are there any ongoing issues related to this?

@juliancoronado
Copy link

I see this issue is closed. Any estimate on when it will hit flutter stable?

@maheshj01
Copy link
Collaborator

@juliancoronado A new stable release occurs every quarter, a fix on master channel would roughly take 5-6 months to land on stable channel Please see https://github.com/flutter/flutter/wiki/Flutter-build-release-channels

@maheshj01
Copy link
Collaborator

@nero-angela, Please consider filing that as a separate issue.

jja08111 added a commit to Foundy-LLC/p_lyric that referenced this issue May 8, 2022
* 기기에서 재생중인 음악의 정보 및 가사를 보여주는 기능 (#10) (#11)

* Import nowplaying plugin

* Change nowplaying service label to PLyric in AndroidManifest.xml

* Implement playing_music_provider.dart

* Implement CardView in HomePage

* Feature/crawling (#12)

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* 크롤링 기능 향상 (#9)

* Add proxy URL for bypass CORS

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Fix invalid importing (#21)

* Update widget test (#22)

* Remove widget test

* Add appBar title widget test

* Add git action (#24)

* Add git action

* Fix code formatting

* Fix file

* Remove test content

* Add scroll button in home page (#13)

Co-authored-by: Sihyun Jung <[email protected]>

* Update scroll button in home page (#25)

* Update scroll button in home page

* Change if branch to ternary operator

* Change parameters (#26)

* Add proxy URL for bypass CORS

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

* Delete test code temporarily

* Fix parameters, from now on it takes

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Change text height and add padding to bottom in scrollview (#28)

* Update nowplaying plugin version to fix #27 (#29)

* PlayingMusicProvider 업데이트 (#30)

* Fix not working error in other players

* Change debouncer to waiter

* Add exported at NowPlaying service in AndroidManifest.xml

* Fix typo

* 홈 화면에 음악 컨트롤 기능 추가 (#32)

* Change minSdkVersion to 16 in build.gradle

* Implement MediaController.kt

* Implement music controller in Flutter part

* Code formatting

* Return false at null exception in MediaController.kt

* Simplify constructor in MediaController.kt

* Add try-catch and change to playOrPause

* Add caption in TODO items

* Change reverse curve to easeIn

* Add album cover image animation

* Change property to getBuilder

* Add animated switcher for album cover transition

* Improve code structure for performance

* Update gradle, kotlin and targetSdk version (#34)

* 멜론 검색 최적화를 위한  전처리 기능 추가 (#36)

* 멜론 검색 최적화를 위한  전처리 기능 추가

* 리뷰를 통해 받은 개선점 적용

Co-authored-by: Cirrus CI <[email protected]>

* Remove duplicate (#37)

* 미디어 초기 상태 버그 해결 및 안정성 향상 (#38)

* Remove duplicate

* Remove useless generic code in MediaController.kt

* Fix to fetch music data when starting

* Update media controller to improve stability

* Bump nowplaying plugin version

* Change to show snack bar when failed control

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가 (#39)

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가

* Delete some unused variable & import

Co-authored-by: Cirrus CI <[email protected]>

* Change to show snack bar when failed control (#41)

* Update snackBar that shows when controller has error (#43)

* 권한 요구 Bottom Sheet 구현 (#42)

* Update permission requiring scenario

* Add lifecycle observer in permission bottom sheet

* Update text height in themes.dart

* Remove duplicated text height

* Remove already defined text height

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리 (#44)

Co-authored-by: Cirrus CI <[email protected]>

* Remove useless test (#46)

* 홈 페이지 컨트롤러 및 가사 갱신 상태 수정 (#45)

* 음악을 재생 혹은 중지시 컨트롤러 위치가 0으로 변경되는 문제 해결

* 다음 혹은 이전곡 재생시 컨트롤 버튼 상태 문제 수정

* Fix typo

* Add areLyricsUpdating value and change track to non-null value

* 가사를 얻고있는 중 인지 계산하는 로직 업데이트

* Rename to trackState

* Change type from List to Set

* 테마 변경시 일부 적용이 안되던 버그 해결 (#47)

* 온라인 폰트에서 폰트 에셋 파일로 변경 (#48)

* Add font files

* Fix font importing and update fonts

* Change font height

* Create default_snack_bar.dart

* Subtitle 구현 (#49)

* Main Change : 벅스로 플랫폼 변경 (#50)

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리

* 영어 한국어 혼용문 처리 로직 변경 & 테스트 케이스 추가

* Change Melon to Bugs platform

* - 문자도 처리하게 변경

* Fix Typo

* 검색 과정 중 오류 핸들링 & 예외 처리 적용

* Delete main function in scraper file & Add documentation

* Fix function typo

Co-authored-by: Cirrus CI <[email protected]>

* 중복된 테스트 삭제 (#51)

* 가사 스크롤 뷰의 bottom 패딩 수정 (#53)

* 특수 문자 로직 변경 (#55)

Co-authored-by: Cirrus CI <[email protected]>

* Conflict resolve by making basic test file (#61)

* Remove widget test (#18)

* Create main.yml (#19)

* Create main.yml

* Add appBar widget test

* Write basic test

Co-authored-by: Minseong Kim <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* Rebase from main branch (#62)

* Remove widget test (#18)

* Create main.yml (#19)

* Create main.yml

* Add appBar widget test

* 기기에서 재생중인 음악의 정보 및 가사를 보여주는 기능 (#10) (#11)

* Import nowplaying plugin

* Change nowplaying service label to PLyric in AndroidManifest.xml

* Implement playing_music_provider.dart

* Implement CardView in HomePage

* Feature/crawling (#12)

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* 크롤링 기능 향상 (#9)

* Add proxy URL for bypass CORS

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Fix invalid importing (#21)

* Add git action (#24)

* Add git action

* Fix code formatting

* Fix file

* Remove test content

* Add scroll button in home page (#13)

Co-authored-by: Sihyun Jung <[email protected]>

* Update scroll button in home page (#25)

* Update scroll button in home page

* Change if branch to ternary operator

* Change parameters (#26)

* Add proxy URL for bypass CORS

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

* Delete test code temporarily

* Fix parameters, from now on it takes

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Change text height and add padding to bottom in scrollview (#28)

* Update nowplaying plugin version to fix #27 (#29)

* PlayingMusicProvider 업데이트 (#30)

* Fix not working error in other players

* Change debouncer to waiter

* Add exported at NowPlaying service in AndroidManifest.xml

* Fix typo

* 홈 화면에 음악 컨트롤 기능 추가 (#32)

* Change minSdkVersion to 16 in build.gradle

* Implement MediaController.kt

* Implement music controller in Flutter part

* Code formatting

* Return false at null exception in MediaController.kt

* Simplify constructor in MediaController.kt

* Add try-catch and change to playOrPause

* Add caption in TODO items

* Change reverse curve to easeIn

* Add album cover image animation

* Change property to getBuilder

* Add animated switcher for album cover transition

* Improve code structure for performance

* Update gradle, kotlin and targetSdk version (#34)

* 멜론 검색 최적화를 위한  전처리 기능 추가 (#36)

* 멜론 검색 최적화를 위한  전처리 기능 추가

* 리뷰를 통해 받은 개선점 적용

Co-authored-by: Cirrus CI <[email protected]>

* Remove duplicate (#37)

* 미디어 초기 상태 버그 해결 및 안정성 향상 (#38)

* Remove duplicate

* Remove useless generic code in MediaController.kt

* Fix to fetch music data when starting

* Update media controller to improve stability

* Bump nowplaying plugin version

* Change to show snack bar when failed control

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가 (#39)

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가

* Delete some unused variable & import

Co-authored-by: Cirrus CI <[email protected]>

* Change to show snack bar when failed control (#41)

* Update snackBar that shows when controller has error (#43)

* 권한 요구 Bottom Sheet 구현 (#42)

* Update permission requiring scenario

* Add lifecycle observer in permission bottom sheet

* Update text height in themes.dart

* Remove duplicated text height

* Remove already defined text height

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리 (#44)

Co-authored-by: Cirrus CI <[email protected]>

* Remove useless test (#46)

* 홈 페이지 컨트롤러 및 가사 갱신 상태 수정 (#45)

* 음악을 재생 혹은 중지시 컨트롤러 위치가 0으로 변경되는 문제 해결

* 다음 혹은 이전곡 재생시 컨트롤 버튼 상태 문제 수정

* Fix typo

* Add areLyricsUpdating value and change track to non-null value

* 가사를 얻고있는 중 인지 계산하는 로직 업데이트

* Rename to trackState

* Change type from List to Set

* 테마 변경시 일부 적용이 안되던 버그 해결 (#47)

* 온라인 폰트에서 폰트 에셋 파일로 변경 (#48)

* Add font files

* Fix font importing and update fonts

* Change font height

* Create default_snack_bar.dart

* Subtitle 구현 (#49)

* Main Change : 벅스로 플랫폼 변경 (#50)

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리

* 영어 한국어 혼용문 처리 로직 변경 & 테스트 케이스 추가

* Change Melon to Bugs platform

* - 문자도 처리하게 변경

* Fix Typo

* 검색 과정 중 오류 핸들링 & 예외 처리 적용

* Delete main function in scraper file & Add documentation

* Fix function typo

Co-authored-by: Cirrus CI <[email protected]>

* 중복된 테스트 삭제 (#51)

* 가사 스크롤 뷰의 bottom 패딩 수정 (#53)

* 특수 문자 로직 변경 (#55)

Co-authored-by: Cirrus CI <[email protected]>

* Conflict resolve by making basic test file (#61)

* Remove widget test (#18)

* Create main.yml (#19)

* Create main.yml

* Add appBar widget test

* Write basic test

Co-authored-by: Minseong Kim <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* 임시 테스트 삭제

Co-authored-by: Sihyun Jung <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* 플로팅 오버레이 윈도우 구현 (#66)

* 오버레이를 위한 권한 추가 및 기반 작업 (#56)

* SystemAlertWindow 권한 및 초기 세팅

* SystemAlertWindow 권한 및 초기 세팅

* SystemAlertWindow 권한 및 초기 세팅

* 권한 허용 후 BottomSheet가 내려가지 않는 문제 수정

* 코드 간결화

* 플로팅 창 전환 아이콘 홈 페이지에 추가

* SystemAlertWindow 플러그인 삭제

* SystemAlertWindow 플러그인 삭제

* Gradle 버전 4.2.2로 다운그레이드

아래에서 관련 이슈 내용 확인 가능
flutter/flutter#87649

* 플로팅 뷰 프로토타입 구현

* Fix permission in AndroidManifest.xml

* Update nowplaying plugin

* 사용자가 명시적으로 권한을 허용 or 건너뛰기를 한 경우만 저장

* HomePage의 축소 버튼 기능 구현

* 포어그라운드 작동 업데이트

* 홈 페이지 축소 버튼 버그 해결 및 플로팅 뷰 안정성 향상

* 플로팅 윈도우 백그라운드 작동 및 앨범 커버 적용 등 업데이트 (#65)

* SystemAlertWindow 권한 및 초기 세팅

* SystemAlertWindow 권한 및 초기 세팅

* SystemAlertWindow 권한 및 초기 세팅

* 권한 허용 후 BottomSheet가 내려가지 않는 문제 수정

* 코드 간결화

* 플로팅 창 전환 아이콘 홈 페이지에 추가

* SystemAlertWindow 플러그인 삭제

* SystemAlertWindow 플러그인 삭제

* Gradle 버전 4.2.2로 다운그레이드

아래에서 관련 이슈 내용 확인 가능
flutter/flutter#87649

* 플로팅 뷰 프로토타입 구현

* Fix permission in AndroidManifest.xml

* Update nowplaying plugin

* 사용자가 명시적으로 권한을 허용 or 건너뛰기를 한 경우만 저장

* HomePage의 축소 버튼 기능 구현

* 포어그라운드 작동 업데이트

* 홈 페이지 축소 버튼 버그 해결 및 플로팅 뷰 안정성 향상

* Update to show album cover and tap gesture handling

- jja08111/nowplaying@a115a93

* 백그라운드에서 작동하도록 수정

- 사용자가 명시적으로 홈 페이지의 축소 버튼을 눌러야만 플로팅 윈도우 서비스 실행
- 앱을 종료해도 음악 업데이트 시 플로팅 뷰를 위한 가사 검색이 작동할 수 있도록 수정

* 플러그인 연결 에러 수정 및 Merge로 인한 잘못된 코드 수정

- jja08111/nowplaying@cace5f5

* 명시적으로 플로팅 뷰를 시작할때만 뷰를 띄우도록 수정

- jja08111/nowplaying@b4666de

* 다크모드 구현 및 초기 진입 에러 예외처리 구현

- jja08111/nowplaying@2cec21a

* Flutter, Dart SDK 버전 업그레이드 및 패키지 버전 업데이트 (#67)

* 설정 페이지에서 권한 설정 타일 구현 (#68)

* PermissionProvider 리팩토링

* 설정 페이지에서 권한 설정 타일 구현

* 권한 요구 BottomSheet 드래그 비허용 및 버전 오류 수정 (#70)

* 드래그하여 권한 요구 BottomSheet 해제하지 못하도록 변경

* permission_handler 플러그인의 버전 업데이트가 반영되지 않은 부분 수정

* 디자인 개선 (#71)

* 잘못된 배경 색상톤 수정

* 카드뷰 배경 투명도 84%로 수정

* 앱 타이틀 텍스트 두께 하향

* 아이콘 투명도 54%로 감소

* 아이콘 테두리 둥근 것으로 수정

- play_pause의 `AnimatedIcon`은 둥근 것이 없어서 애니메이션 삭제

* Subtitle 왼쪽 막대 수정

* Card 위젯의 그림자 수정

* 다크모드일때 잘못된 카드뷰 배경 색상 수정

* Add const keyword

* Upgrade flutter, dart version and fix NameNotFoundException warning (#73)

* Bump flutter and dart version

* Fix NameNotFoundException by adding permission in AndroidManifest.xml

* 특수문자 query 생성 시 처리하기 (#72)

* 검색 쿼리를 Uri encode 하는 형식으로 변경

* Handle special characters for search query

* Add necessary files for #72

* Apply suggestions from #72

* Apply suggestions from #72 - Move Exception class in bugs_lyrics_scraper.dart

* Apply suggestion from #72 - Move exception classes in bugs_lyrics_scraper.dart

* solve getLyricsFromBugs data type problem

* Delete fromGenius & Delete Exception class & Change variable name

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: jungsiroo <[email protected]>

* 배너 광고 추가 (#74)

* Bump flutter and dart version

* Fix NameNotFoundException by adding permission in AndroidManifest.xml

* Add banner ad id in AndroidManifest.xml

* 배너 광고 구현

* 광고가 없을 때 공백을 두지 않도록 수정

* 광고가 없을 때 광고가 띄워지지 않는 것을 테스트

* 잘못된 광고 ID 제거 in AndroidManifest.xml

* 광고 ID 및 광고 단위 ID 변경

* 앱 이름 및 아이콘 변경 (#77)

* 앱이름 PLyric 으로 수정

* 앱 아이콘 변경

* 안드로이드 초기출시 준비 (#78)

* 검색 쿼리를 Uri encode 하는 형식으로 변경

* Handle special characters for search query

* Add necessary files for #72

* Apply suggestions from #72

* Apply suggestions from #72 - Move Exception class in bugs_lyrics_scraper.dart

* Apply suggestion from #72 - Move exception classes in bugs_lyrics_scraper.dart

* solve getLyricsFromBugs data type problem

* Delete fromGenius & Delete Exception class & Change variable name

* 안드로이드 출시를 위한 난독화, 암호화와 앱 서명

* README 파일 업데이트

* 패키지 이름 변경

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: jungsiroo <[email protected]>

* Gitignore에 `key.jks` 추가 및 누락된 패키지 이름 변경 수정 (#79)

* Update gitignore

* Update .gitignore

* Revert "Update gitignore"

This reverts commit 9c7ef5a.

* 누락된 패키지 이름 수정 반영

* 광고 성능 향상을 위한 임시 해결 및 버전 1.0.0+2로 수정 (#80)

* 광고로 인한 성능저하 임시 해결

googleads/googleads-mobile-flutter#269
에 있는 fork 를 이용하여 임시 해결

* 설정 화면에서 배너 광고 제거

* TODO 추가

* Bump version to 1.0.0+2

* Fix typo in README.md

* 노래 데이터 전처리 클래스 적용 및 단순화 (#81)

* 검색 쿼리를 Uri encode 하는 형식으로 변경

* Handle special characters for search query

* Add necessary files for #72

* Apply suggestions from #72

* Apply suggestions from #72 - Move Exception class in bugs_lyrics_scraper.dart

* Apply suggestion from #72 - Move exception classes in bugs_lyrics_scraper.dart

* solve getLyricsFromBugs data type problem

* Delete fromGenius & Delete Exception class & Change variable name

* 안드로이드 출시를 위한 난독화, 암호화와 앱 서명

* README 파일 업데이트

* 패키지 이름 변경

* Apply Song data preprocessor method to music provider

* Revert "Apply Song data preprocessor method to music provider"

This reverts commit 6a3e924.

* 전처리 과정 `getLyricsFromBugs()` 함수에 추가

* extension을 이용하여 SongDataFilter로 수정

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: jungsiroo <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Bump version to 1.0.0+3 (#83)

* 광고 ID 수정 및 공개 테스트 출시(1.0.0+4) (#85)

* 광고 코드 수정

* Bump version to 1.0.0+4

* Conflict 해결 (#87)

* Remove widget test (#18)

* Create main.yml (#19)

* Create main.yml

* Add appBar widget test

* 초기 구현 (#64)

* 기기에서 재생중인 음악의 정보 및 가사를 보여주는 기능 (#10) (#11)

* Import nowplaying plugin

* Change nowplaying service label to PLyric in AndroidManifest.xml

* Implement playing_music_provider.dart

* Implement CardView in HomePage

* Feature/crawling (#12)

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* 크롤링 기능 향상 (#9)

* Add proxy URL for bypass CORS

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Fix invalid importing (#21)

* Add git action (#24)

* Add git action

* Fix code formatting

* Fix file

* Remove test content

* Add scroll button in home page (#13)

Co-authored-by: Sihyun Jung <[email protected]>

* Update scroll button in home page (#25)

* Update scroll button in home page

* Change if branch to ternary operator

* Change parameters (#26)

* Add proxy URL for bypass CORS

* Add proxy URL for bypass CORS (#4)

Co-authored-by: Cirrus CI <[email protected]>

* Revert "Add proxy URL for bypass CORS (#4)"

This reverts commit d78b5d1

* Add internet permission in AndroidManifest.xml

* Add Some useless file to gitignore

* 멜론 크롤링 기능 로직 향상, 폴더 이름 수정, 기존 searchLyric 함수 세분화

* Rollback .gitignore file

* Rollback pubspec.lock file

* Delete unuse import(meterial)

* Edit comment, Add TODO

* Enhance comments refer to https://dart.dev/guides/language/effective-dart/documentation

* Exclude further explanation about some variables

* Fix comments

* Remove duplicate comment

* Delete test code temporarily

* Fix parameters, from now on it takes

Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: Minseong Kim <[email protected]>

* Change text height and add padding to bottom in scrollview (#28)

* Update nowplaying plugin version to fix #27 (#29)

* PlayingMusicProvider 업데이트 (#30)

* Fix not working error in other players

* Change debouncer to waiter

* Add exported at NowPlaying service in AndroidManifest.xml

* Fix typo

* 홈 화면에 음악 컨트롤 기능 추가 (#32)

* Change minSdkVersion to 16 in build.gradle

* Implement MediaController.kt

* Implement music controller in Flutter part

* Code formatting

* Return false at null exception in MediaController.kt

* Simplify constructor in MediaController.kt

* Add try-catch and change to playOrPause

* Add caption in TODO items

* Change reverse curve to easeIn

* Add album cover image animation

* Change property to getBuilder

* Add animated switcher for album cover transition

* Improve code structure for performance

* Update gradle, kotlin and targetSdk version (#34)

* 멜론 검색 최적화를 위한  전처리 기능 추가 (#36)

* 멜론 검색 최적화를 위한  전처리 기능 추가

* 리뷰를 통해 받은 개선점 적용

Co-authored-by: Cirrus CI <[email protected]>

* Remove duplicate (#37)

* 미디어 초기 상태 버그 해결 및 안정성 향상 (#38)

* Remove duplicate

* Remove useless generic code in MediaController.kt

* Fix to fetch music data when starting

* Update media controller to improve stability

* Bump nowplaying plugin version

* Change to show snack bar when failed control

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가 (#39)

* 테스트 코드 추가 & 전처리해주는 클래스 메소드에서 return 값에 trim() 추가

* Delete some unused variable & import

Co-authored-by: Cirrus CI <[email protected]>

* Change to show snack bar when failed control (#41)

* Update snackBar that shows when controller has error (#43)

* 권한 요구 Bottom Sheet 구현 (#42)

* Update permission requiring scenario

* Add lifecycle observer in permission bottom sheet

* Update text height in themes.dart

* Remove duplicated text height

* Remove already defined text height

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리 (#44)

Co-authored-by: Cirrus CI <[email protected]>

* Remove useless test (#46)

* 홈 페이지 컨트롤러 및 가사 갱신 상태 수정 (#45)

* 음악을 재생 혹은 중지시 컨트롤러 위치가 0으로 변경되는 문제 해결

* 다음 혹은 이전곡 재생시 컨트롤 버튼 상태 문제 수정

* Fix typo

* Add areLyricsUpdating value and change track to non-null value

* 가사를 얻고있는 중 인지 계산하는 로직 업데이트

* Rename to trackState

* Change type from List to Set

* 테마 변경시 일부 적용이 안되던 버그 해결 (#47)

* 온라인 폰트에서 폰트 에셋 파일로 변경 (#48)

* Add font files

* Fix font importing and update fonts

* Change font height

* Create default_snack_bar.dart

* Subtitle 구현 (#49)

* Main Change : 벅스로 플랫폼 변경 (#50)

* TEST CASE 추가 & 곡에 포함되있는 특수 문자도 같이 처리

* 영어 한국어 혼용문 처리 로직 변경 & 테스트 케이스 추가

* Change Melon to Bugs platform

* - 문자도 처리하게 변경

* Fix Typo

* 검색 과정 중 오류 핸들링 & 예외 처리 적용

* Delete main function in scraper file & Add documentation

* Fix function typo

Co-authored-by: Cirrus CI <[email protected]>

* 중복된 테스트 삭제 (#51)

* 가사 스크롤 뷰의 bottom 패딩 수정 (#53)

* 특수 문자 로직 변경 (#55)

Co-authored-by: Cirrus CI <[email protected]>

* Conflict resolve by making basic test file (#61)

* Remove widget test (#18)

* Create main.yml (#19)

* Create main.yml

* Add appBar widget test

* Write basic test

Co-authored-by: Minseong Kim <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* 임시 테스트 삭제

Co-authored-by: Sihyun Jung <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* Delete song_data_preprocessor.dart

Co-authored-by: Sihyun Jung <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>

* `QUERY_ALL_PACKAGES` 권한 제거 및 쿼리 추가 in `AndroidManifest.xml` (#93)

* 앱 권한 수정

* 앱 버전 수정

* SDK 업데이트

* Deprecated된 색상 theme 수정

Co-authored-by: Sihyun Jung <[email protected]>
Co-authored-by: Cirrus CI <[email protected]>
Co-authored-by: jungsiroo <[email protected]>
@dayron9110
Copy link

With Flutter 3.0 the performance on iOS is so bad...

@ramonpaolo
Copy link

ramonpaolo commented Jul 3, 2022

I have the same issue

Out of nowhere, when I open my app, the ad(banner) starts dropping FPS(from 59-60 to 28-29). The performance improves, when I remove the Banner from the Widget tree (the Banner is pinned to a bottomSheet)

I'm using Flutter verion 2.10.4 * channel stable with google_mobile_ads: 0.13.6

Edit: I'm using now:

google_mobile_ads: 
    git:
      url: https://github.com/SuaMusica/googleads-mobile-flutter.git
      path: packages/google_mobile_ads
      ref: feature/suamusica

and it looks like the performance issue is gone

@iqfareez
Copy link

iqfareez commented Jul 3, 2022

@ramonpaolo Try upgrading to Flutter 3

@euphio
Copy link

euphio commented Jul 20, 2022

The obvious solution to this appears to be implementing a native flutter (widget) ad container rather than relying on Platform Views.

Flutter platform views evidently aren’t performant enough at present to support native ads (flutter/flutter#107486 (comment)), particularly in a ListView which is one of the documented usages of displaying Native Advanced ads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working e3-weeks Effort: < 4 weeks fixed Issue has been resolved and pull request linked p2-medium performance issue covers plugin performance platform-android Android applications specifically
Projects
None yet
Development

No branches or pull requests