diff --git a/AGENTS.md b/AGENTS.md index 334e5e1..5b83301 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,6 +98,10 @@ branch names, credentials, signing material, or other secrets. - After creating and verifying a release, update the corresponding F-Droid submission/build recipe when required. The repository's F-Droid-related files are for reproducible verification, not an excuse to maintain a duplicate unused build path. +- MegaProxyIdentity SVGs are the sole source for owned artwork; screenshots and the local GitHub + download badge are exceptions. Regenerate committed PNG/XML with + `bundle exec fastlane android update_graphics [ref:FULL_SHA]`; keep source/output hashes in + `assets/branding/identity-lock.json`. Normal builds must remain offline with respect to artwork. - Store F-Droid listing metadata and current store artwork under `fastlane/metadata/android`. Obsolete artwork does not need an archive copy in the working tree because Git preserves history. diff --git a/README.md b/README.md index 607065f..5079ae6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Android 8+](https://img.shields.io/badge/Android-8.0%2B-3DDC84?logo=android&logoColor=white)](https://developer.android.com/about/versions/oreo)

- MegaProxy app icon + MegaProxy app icon

MegaProxy is an open-source Android VPN client for HTTPS, SOCKS5, MASQUE α (HTTP/3) diff --git a/app/src/main/res/drawable-hdpi/ic_vpn_notification.png b/app/src/main/res/drawable-hdpi/ic_vpn_notification.png new file mode 100644 index 0000000..c7fdbb9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_vpn_notification.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_vpn_notification.png b/app/src/main/res/drawable-mdpi/ic_vpn_notification.png new file mode 100644 index 0000000..6ad51ed Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_vpn_notification.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_vpn_notification.png b/app/src/main/res/drawable-xhdpi/ic_vpn_notification.png new file mode 100644 index 0000000..c0fd351 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_vpn_notification.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_vpn_notification.png b/app/src/main/res/drawable-xxhdpi/ic_vpn_notification.png new file mode 100644 index 0000000..788b021 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_vpn_notification.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_vpn_notification.png b/app/src/main/res/drawable-xxxhdpi/ic_vpn_notification.png new file mode 100644 index 0000000..8eb65db Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_vpn_notification.png differ diff --git a/app/src/main/res/drawable/ic_vpn_notification.xml b/app/src/main/res/drawable/ic_vpn_notification.xml deleted file mode 100644 index 82e7e17..0000000 --- a/app/src/main/res/drawable/ic_vpn_notification.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index be43858..f882cd8 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ + - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index be43858..f882cd8 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ + - + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 4e5afda..2cc06bc 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..6ad2f93 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index f850e5e..81a9d77 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 4e5afda..be2c10c 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index ef2d792..336214e 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..b7c609c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index d1c5c40..1a1ccf4 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index ef2d792..8a02ac4 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 17cc7ad..a10416d 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..c5d2f00 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index fa349ec..049ae0e 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 17cc7ad..5dd4469 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index abb52ab..86ccc37 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..0148bbb Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 79bfcc7..945c2cc 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index abb52ab..bd1dcc3 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 39e3331..3e05f27 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..df20bf6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 2709ad9..296fb78 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 39e3331..9283b7b 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/ic_launcher_colors.xml b/app/src/main/res/values/ic_launcher_colors.xml deleted file mode 100644 index 5d60d0a..0000000 --- a/app/src/main/res/values/ic_launcher_colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #2725D9 - diff --git a/assets/branding/IDENTITY-LICENSE b/assets/branding/IDENTITY-LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/assets/branding/IDENTITY-LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/assets/branding/README.md b/assets/branding/README.md index bb49b25..e89a4b4 100644 --- a/assets/branding/README.md +++ b/assets/branding/README.md @@ -1,17 +1,14 @@ -# MegaProxy branding assets +# Generated MegaProxy artwork -`megaproxy-launcher-master.png` is the current canonical raster artwork for the MegaProxy logo. -Use it as the visual source when regenerating derived application, store, or documentation assets. +[MegaProxyIdentity](https://github.com/andre487/MegaProxyIdentity) SVGs are the source of truth. +The retired raster masters are archived there under `Generative/AndroidMegaProxy`. -Derived assets live with the component that consumes them: +Run `bundle exec fastlane android update_graphics` to regenerate committed Android, store +and documentation artwork. `identity-lock.json` records the Identity revision, source/output +SHA-256 hashes and renderer version. `IDENTITY-LICENSE` applies to Identity-derived artwork. +Normal app builds use committed outputs and never download artwork. -- Android launcher resources: `app/src/main/res/mipmap-*` -- Store listing icon and graphics: `fastlane/metadata/android/*/images/` -- Repository and documentation graphics: `docs/assets/` +The GitHub badge remains a local exception: `docs/assets/get-it-on-github.svg` and its +notices stay here; its PNG is generated by the same command. Screenshots are maintained separately. -## GitHub download badge - -`docs/assets/get-it-on-github.svg`, its PNG and notices are copied from AndroidMessage487. -The SVG contains outlined text; third-party attribution is in -[get-it-on-github-NOTICES.txt](../../docs/assets/get-it-on-github-NOTICES.txt). -README and installation guides use the PNG at a display height of 75 pixels. +Instructions: [English](../../docs/en/graphics.md) · [Русский](../../docs/ru/graphics.md). diff --git a/assets/branding/identity-lock.json b/assets/branding/identity-lock.json new file mode 100644 index 0000000..6dc3060 --- /dev/null +++ b/assets/branding/identity-lock.json @@ -0,0 +1,52 @@ +{ + "repository": "https://github.com/andre487/MegaProxyIdentity", + "commit": "fc236e43659602c3093726521d7c25bdd3536fe7", + "renderer": "rsvg-convert version 2.62.3\n\nlibraries used:\n cairo 1.18.6\n pango 1.58.2\n harfbuzz 14.6.0\n fontconfig 2.18.3", + "sources": { + "SVG/icon.svg": "193fe4e0848f934a17d095d2adc874430bb5a3b1311b25b4e614ff4918742299", + "SVG/icon-radius.svg": "7c276c805dbd641569d838831fcc355e671f9d17e577bcba1be22120978414e4", + "SVG/icon-round.svg": "21709d03ca94deb643f27c28765419a88fde0ff93564b347b61879e306e50d81", + "SVG/feature-graphic.svg": "cb76a52ba46da5e0ee63821325d5481731f17436059c9c026111d41c37a412a0", + "SVG/icon-taskbar.svg": "7ef9047c29cbdd67bf4553f2896f766b18e0c6a4d8b6bcc5c4f6fbbc32f27411", + "LICENSE": "a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499" + }, + "local_sources": { + "docs/assets/get-it-on-github.svg": "a3463008b42db57aede368deb5cfb5d4b137c318f04d8fead00118bc64263693" + }, + "outputs": { + "app/src/main/res/drawable-hdpi/ic_vpn_notification.png": "7b09c81a50e04a8b69bc67640f1362124b41bc0d7d887d06f46a7d15558e9969", + "app/src/main/res/drawable-mdpi/ic_vpn_notification.png": "45e99b714a2e87796fd6e6d791fa78a517587db31cd3439d68ae3716b595e2af", + "app/src/main/res/drawable-xhdpi/ic_vpn_notification.png": "c3cd48a947e5a8413ac2bc110a1096e7f8543b616e82525bd2e8e99c5d6d52d0", + "app/src/main/res/drawable-xxhdpi/ic_vpn_notification.png": "76e6a1583c280687a738910bea575ff2b32da5ceeb161ba33fd667b1ba9df2b5", + "app/src/main/res/drawable-xxxhdpi/ic_vpn_notification.png": "07f566dd7c14ea91b8850663ab625c73f442ab8dc28e5870664ba20dfb147583", + "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml": "c4fe288de664c1d03457499c5e5c060695265f705ee0ab3222668efd7044bbc1", + "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml": "c4fe288de664c1d03457499c5e5c060695265f705ee0ab3222668efd7044bbc1", + "app/src/main/res/mipmap-hdpi/ic_launcher.png": "07c3d4c38716577c9f42886e1d85fcc0af921cf56ee674fe4e44f15fa097fd86", + "app/src/main/res/mipmap-hdpi/ic_launcher_background.png": "b7bc17dbd5345a7b87c45ea6f1caae55a4dd7b5c283b420361b28b3ce604d2f9", + "app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png": "397b4ada2959790f22525dc2d3096e7981dcae39e3a9dfa0def9012cf8b3f74b", + "app/src/main/res/mipmap-hdpi/ic_launcher_round.png": "248599c82f2035734a16418794f73bc7399839cf2f5b9a1ffce7fa166128d416", + "app/src/main/res/mipmap-mdpi/ic_launcher.png": "a47be352b759e1251049b7cf947ac608c892a2606de5196ceabdbeca622679f4", + "app/src/main/res/mipmap-mdpi/ic_launcher_background.png": "06beddd6cb61455d1f8e3003a018141c83683266e9a189900c81ceae3d43784f", + "app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png": "d533928a889c31741a2688819c3644658bd953e31af09c7d19bb0dbaa90478c3", + "app/src/main/res/mipmap-mdpi/ic_launcher_round.png": "f7636b8f2be5dc929d3ad8f5cc7b347757dc0054612aea9ae5f391cf69f40d54", + "app/src/main/res/mipmap-xhdpi/ic_launcher.png": "e3f75e60d2fc048b252fea353468d3b6f9e609a8f68b17100728426619cff52b", + "app/src/main/res/mipmap-xhdpi/ic_launcher_background.png": "33e9efa0c5ec99b33e1ed09cfb352f0f88d4a744556176fd60d3dcd8203de2d5", + "app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png": "6c65f3a6c30062a33506f25fef78af602c34ac73979d5b803ecb58ccaefe8b1f", + "app/src/main/res/mipmap-xhdpi/ic_launcher_round.png": "c35ce610605451931a91f8daed0f29ea0717a1dd3c6516e4093362512c8e5601", + "app/src/main/res/mipmap-xxhdpi/ic_launcher.png": "ae5b1f6eb4155b6c0a57cf8d4ff5c1373d6c6cf7b2679ca188343b910136d052", + "app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png": "2087baf37a52a2bcb9eb7871f8e8cdd3f6c7ea5d0c6d960bdb177184628c4ebd", + "app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png": "a9d96a7e692414a55a954eb684d669eb927711189622f9faf7ee5eba6912cc22", + "app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png": "985db1ecd8cd245b3225b7eeda465ecc4dce2c2d736e3f2e11699f780c29574f", + "app/src/main/res/mipmap-xxxhdpi/ic_launcher.png": "34d31c5c2a82c08432fba53cd06b9acae09eb704db9759cca102664a56ca45df", + "app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png": "63885f095aa8a529a1d03e7219bcf5af6ff8b662e9ebf2bf8a8efdc03ff4eb54", + "app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png": "fb47c5ce427b8c8cc0c8435a2f3f5ab6ba6864404986416280f554ad7f225242", + "app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png": "ddfdbdd28730b37e127f6f20b46a505f8e63fc490298e1b5e8d2b592541c181a", + "assets/branding/IDENTITY-LICENSE": "a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499", + "docs/assets/get-it-on-github.png": "6f5cca8d2a224beff6ef2310c406dbda776f6f02ce6deb8791b30675df9e1da6", + "docs/assets/social-preview.png": "f435ead9047b3fbe29f856f10db3b1ba7a8cf8801242ba331534008c6aa36527", + "fastlane/metadata/android/en-US/images/featureGraphic.png": "6651973cc2b3432d319c131742645d427874944e2b72b5aa9db9c939426fe9b6", + "fastlane/metadata/android/en-US/images/icon.png": "0c4b997f0ee660e9ba677df0c30d89647afce787996ba42f156674adf25534bd", + "fastlane/metadata/android/ru-RU/images/featureGraphic.png": "6651973cc2b3432d319c131742645d427874944e2b72b5aa9db9c939426fe9b6", + "fastlane/metadata/android/ru-RU/images/icon.png": "0c4b997f0ee660e9ba677df0c30d89647afce787996ba42f156674adf25534bd" + } +} diff --git a/assets/branding/megaproxy-launcher-master.png b/assets/branding/megaproxy-launcher-master.png deleted file mode 100644 index 8f5eb7c..0000000 Binary files a/assets/branding/megaproxy-launcher-master.png and /dev/null differ diff --git a/docs/assets/social-preview.png b/docs/assets/social-preview.png index 8109769..5e72f56 100644 Binary files a/docs/assets/social-preview.png and b/docs/assets/social-preview.png differ diff --git a/docs/en/fastlane.md b/docs/en/fastlane.md index 2f30e12..c9433a0 100644 --- a/docs/en/fastlane.md +++ b/docs/en/fastlane.md @@ -10,6 +10,7 @@ explicit `JAVA_HOME` fails early. `bundle exec fastlane lanes` lists the checked | Command | Result | | --- | --- | | `bundle exec fastlane android renew_config_schema` | Downloads and pins the current MegaProxyConfig schemas and examples; use `ref:FULL_SHA` for a reviewed revision. | +| `bundle exec fastlane android update_graphics` | Generates Android/store/docs artwork from MegaProxyIdentity SVGs; use `ref:FULL_SHA` to reproduce a pinned revision. | | `bundle exec fastlane android python_format` | Formats Python scripts with pinned Black and isort. | | `bundle exec fastlane android python_tests` | Runs Python unit tests. | | `bundle exec fastlane android python_checks` | Checks Python formatting/import order and runs unit tests. | diff --git a/docs/en/graphics.md b/docs/en/graphics.md new file mode 100644 index 0000000..f5e3ecd --- /dev/null +++ b/docs/en/graphics.md @@ -0,0 +1,45 @@ +# Updating artwork + +[Русский](../ru/graphics.md) · [Fastlane commands](fastlane.md) + +[MegaProxyIdentity](https://github.com/andre487/MegaProxyIdentity) is the source of truth for +MegaProxy artwork. Edit its SVGs, then regenerate AndroidMegaProxy's committed PNG/XML outputs. +The only local graphic source is the GitHub download badge SVG with its notices. Screenshots +remain separate and are not overwritten. Retired originals, including the old social preview, +are archived in Identity's `Generative/AndroidMegaProxy`; archived PNGs are not generation inputs. + +Install `gh` and authenticate it, install librsvg's `rsvg-convert` (`brew install librsvg` on +macOS or `sudo apt-get install librsvg2-bin` on Debian/Ubuntu), and use the project's Bundler setup. +Python uses only the standard library; generation does not require Pillow or ImageMagick. + +```sh +bundle exec fastlane android update_graphics +# Reproduce a reviewed Identity revision: +bundle exec fastlane android update_graphics ref:FULL_SHA +``` + +The lane calls `scripts/update_graphics.py`. It resolves one Identity commit and downloads all +inputs at that SHA before rendering. It replaces outputs only after every download and render +succeeds. Review the images and commit them together with `assets/branding/identity-lock.json`. +The lock records source/output SHA-256 hashes and the renderer version; use the same librsvg +version for byte-for-byte reproduction. Normal app/F-Droid builds and CI use committed outputs, +without downloading Identity or running a renderer. + +| Source | Generated outputs | +| --- | --- | +| `SVG/icon.svg` | 512×512 store icons and adaptive background/foreground layers | +| `SVG/icon-radius.svg`, `SVG/icon-round.svg` | Legacy square/round launcher icons for all five densities | +| `SVG/icon-taskbar.svg` | White alpha-only notification icons at 24dp for all five densities | +| `SVG/feature-graphic.svg` | 1024×500 EN/RU store feature graphics and a centered 1280×640 social preview | +| Local `docs/assets/get-it-on-github.svg` | 1292×500 GitHub download badge PNG; local notices are retained | + +Adaptive layers retain the SVG's background/rings and keep the wordmark inside Android's safe +circle by scaling it to 75% of the 108dp foreground canvas. The extraction uses the Identity +`icon-2` and `Background` layer IDs; a changed SVG structure requires reviewing the recipe. +The social preview fills its canvas with a small centered crop, without stretching the SVG. +Identity-derived artwork retains the upstream license in `assets/branding/IDENTITY-LICENSE`; +the GitHub badge retains its separate notices. + +`bundle exec fastlane android python_checks` verifies committed hashes, output dimensions, +source pinning, layer extraction and preservation of existing files when rendering fails. +It also rejects untracked-by-manifest artwork outside screenshots and the GitHub badge source. diff --git a/docs/ru/fastlane.md b/docs/ru/fastlane.md index 1e1eaba..691bee3 100644 --- a/docs/ru/fastlane.md +++ b/docs/ru/fastlane.md @@ -10,6 +10,7 @@ Ruby 3.4.10 (`.ruby-version`), затем выполните `bundle install`. | Команда | Результат | | --- | --- | | `bundle exec fastlane android renew_config_schema` | Загружает и фиксирует схемы и примеры MegaProxyConfig; `ref:FULL_SHA` выбирает проверенную ревизию. | +| `bundle exec fastlane android update_graphics` | Генерирует графику Android/магазина/документации из SVG MegaProxyIdentity; `ref:FULL_SHA` воспроизводит закреплённую версию. | | `bundle exec fastlane android python_format` | Форматирует Python зафиксированными Black и isort. | | `bundle exec fastlane android python_tests` | Запускает Python unit-тесты. | | `bundle exec fastlane android python_checks` | Проверяет форматирование, порядок импортов и Python-тесты. | diff --git a/docs/ru/graphics.md b/docs/ru/graphics.md new file mode 100644 index 0000000..d02b241 --- /dev/null +++ b/docs/ru/graphics.md @@ -0,0 +1,45 @@ +# Обновление графики + +[English](../en/graphics.md) · [Команды Fastlane](fastlane.md) + +[MegaProxyIdentity](https://github.com/andre487/MegaProxyIdentity) — источник истины для графики +MegaProxy. Правьте SVG там, затем генерируйте PNG/XML, которые коммитятся в AndroidMegaProxy. +Единственный локальный графический исходник — SVG кнопки GitHub с лицензиями. Скриншоты +обновляются отдельно и не перезаписываются. Старые оригиналы, включая прежний social preview, +сохранены в `Generative/AndroidMegaProxy` репозитория Identity; архивные PNG не участвуют в генерации. + +Установите `gh` и авторизуйтесь, установите `rsvg-convert` из librsvg (`brew install librsvg` на +macOS или `sudo apt-get install librsvg2-bin` на Debian/Ubuntu) и настройте Bundler проекта. +Python использует только стандартную библиотеку; Pillow и ImageMagick не нужны. + +```sh +bundle exec fastlane android update_graphics +# Воспроизвести проверенную версию Identity: +bundle exec fastlane android update_graphics ref:FULL_SHA +``` + +Lane вызывает `scripts/update_graphics.py`. Скрипт определяет один коммит Identity и получает +все исходники по этому SHA. Файлы заменяются только после успешного скачивания и рендеринга +всех изображений. Проверьте результат и закоммитьте его вместе с +`assets/branding/identity-lock.json`. Манифест содержит SHA-256 исходников и результатов, а также +версию рендерера; для побайтового воспроизведения используйте ту же версию librsvg. +Обычные сборки приложения/F-Droid и CI используют готовые файлы из Git, без скачивания Identity +и без запуска рендерера. + +| Исходник | Результат | +| --- | --- | +| `SVG/icon.svg` | Иконки магазина 512×512 и слои фона/переднего плана adaptive icon | +| `SVG/icon-radius.svg`, `SVG/icon-round.svg` | Обычные и круглые launcher-иконки для пяти плотностей | +| `SVG/icon-taskbar.svg` | Белые значки уведомлений с прозрачностью размером 24dp для пяти плотностей | +| `SVG/feature-graphic.svg` | Store feature graphic EN/RU 1024×500 и social preview 1280×640 | +| Локальный `docs/assets/get-it-on-github.svg` | PNG кнопки GitHub 1292×500; локальные лицензии сохраняются | + +Adaptive icon сохраняет фон и кольца SVG, а логотип уменьшается до 75% холста 108dp, чтобы +вписаться в безопасную окружность Android. Извлечение использует ID слоёв `icon-2` и `Background`; +изменение структуры SVG требует проверки рецепта. Social preview заполняет холст с небольшой +центральной обрезкой без растягивания SVG. Лицензия Identity сохраняется в +`assets/branding/IDENTITY-LICENSE`; у кнопки GitHub остаются отдельные лицензии. + +`bundle exec fastlane android python_checks` проверяет хеши готовых файлов, размеры, закрепление +исходников, разделение слоёв и сохранность старых файлов при ошибке рендеринга. Изображения, +не перечисленные в манифесте, запрещены, кроме скриншотов и SVG кнопки GitHub. diff --git a/fastlane/Fastfile b/fastlane/Fastfile index af3bc16..1be3e04 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,6 +12,11 @@ platform :android do sh(ENV.fetch("PYTHON", "python3"), File.join(project_root, "scripts/renew_config_schema.py"), "--ref", options.fetch(:ref, "main").to_s) end + desc "Regenerate committed artwork from MegaProxyIdentity SVGs" + lane :update_graphics do |options| + sh(ENV.fetch("PYTHON", "python3"), File.join(project_root, "scripts/update_graphics.py"), "--ref", options.fetch(:ref, "main").to_s) + end + desc "Run the native Go tests with the race detector" lane :native_tests do report = File.join(project_root, "test-results/native/unit.xml") diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png index be88ec0..f49cfa1 100644 Binary files a/fastlane/metadata/android/en-US/images/featureGraphic.png and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png index e373bff..9e4c54e 100644 Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/ru-RU/images/featureGraphic.png b/fastlane/metadata/android/ru-RU/images/featureGraphic.png index be88ec0..f49cfa1 100644 Binary files a/fastlane/metadata/android/ru-RU/images/featureGraphic.png and b/fastlane/metadata/android/ru-RU/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/ru-RU/images/icon.png b/fastlane/metadata/android/ru-RU/images/icon.png index e373bff..9e4c54e 100644 Binary files a/fastlane/metadata/android/ru-RU/images/icon.png and b/fastlane/metadata/android/ru-RU/images/icon.png differ diff --git a/scripts/tests/test_update_graphics.py b/scripts/tests/test_update_graphics.py new file mode 100644 index 0000000..f460111 --- /dev/null +++ b/scripts/tests/test_update_graphics.py @@ -0,0 +1,131 @@ +import hashlib +import json +import struct +import sys +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).parents[1])) +import update_graphics as m + +ICON = b""" + +""" + + +class UpdateGraphicsTest(unittest.TestCase): + def github(self, path): + self.calls.append(path) + if path == "commits/main": + return json.dumps({"sha": "a" * 40}).encode() + return b"license\n" if "contents/LICENSE?" in path else ICON + + def test_pins_all_sources_and_renders_before_replacing_files(self): + for fail in (False, True): + with self.subTest(fail=fail), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + badge = root / m.BADGE + badge.parent.mkdir(parents=True) + badge.write_bytes(ICON) + target = root / "docs/assets/social-preview.png" + target.write_bytes(b"existing") + retired = root / "assets/branding/megaproxy-launcher-master.png" + retired.parent.mkdir(parents=True) + retired.write_bytes(b"original") + self.calls = [] + sizes = [] + + def render(content, width, height, directory): + sizes.append((width, height)) + if fail and len(sizes) == 3: + raise RuntimeError("render failed") + return b"PNG" + struct.pack(">II", width, height) + content + + with ( + patch.object(m, "github", self.github), + patch.object(m.shutil, "which", return_value="rsvg-convert"), + patch.object( + m.subprocess, + "run", + return_value=SimpleNamespace(stdout="rsvg-convert test\n"), + ), + patch.object(m, "render", render), + ): + if fail: + with self.assertRaises(RuntimeError): + m.update("main", root) + self.assertEqual(b"existing", target.read_bytes()) + self.assertTrue(retired.exists()) + self.assertFalse((root / m.LOCK).exists()) + continue + m.update("main", root) + lock = json.loads((root / m.LOCK).read_text()) + self.assertEqual("a" * 40, lock["commit"]) + self.assertEqual("commits/main", self.calls[0]) + self.assertTrue( + all(path.endswith("?ref=" + "a" * 40) for path in self.calls[1:]) + ) + self.assertEqual(len(m.SOURCES) + 1, len(self.calls)) + self.assertIn((1280, 640), sizes) + self.assertIn((432, 432), sizes) + self.assertFalse(retired.exists()) + for path, checksum in lock["outputs"].items(): + self.assertEqual(checksum, m.digest((root / path).read_bytes())) + + def test_adaptive_layers_preserve_center_and_reject_changed_structure(self): + background, foreground = map(m.svg, m.adaptive_layers(ICON)) + self.assertIsNone(background.find(f".//*[@id='logo']")) + self.assertIsNone(foreground.find(f".//*[@id='Background']")) + self.assertIsNotNone(foreground.find(f".//*[@id='logo']")) + self.assertEqual("-20 -20 160 160", foreground.get("viewBox")) + with self.assertRaises(ValueError): + m.adaptive_layers(ICON.replace(b'id="Background"', b'id="renamed"')) + with self.assertRaises(ValueError): + m.svg( + b'' + ) + + def test_committed_artwork_has_exact_recorded_bytes_and_no_extra_masters(self): + lock = json.loads((m.ROOT / m.LOCK).read_text()) + self.assertRegex(lock["commit"], r"^[a-f0-9]{40}$") + for path, checksum in (lock["outputs"] | lock["local_sources"]).items(): + self.assertEqual( + checksum, hashlib.sha256((m.ROOT / path).read_bytes()).hexdigest(), path + ) + artwork = { + str(path.relative_to(m.ROOT)) + for folder in ( + "assets", + "docs/assets", + "fastlane/metadata", + "app/src/main/res", + ) + for path in (m.ROOT / folder).rglob("*") + if path.suffix.lower() in (".png", ".svg", ".webp", ".jpg", ".jpeg", ".ico") + and "phoneScreenshots" not in path.parts + } + self.assertEqual( + artwork, + {path for path in lock["outputs"] if path.endswith(".png")} + | set(lock["local_sources"]), + ) + for density, scale in m.DENSITIES.items(): + for name, size in ( + ("ic_launcher", 48), + ("ic_launcher_round", 48), + ("ic_launcher_foreground", 108), + ("ic_launcher_background", 108), + ): + data = ( + m.ROOT / f"app/src/main/res/mipmap-{density}/{name}.png" + ).read_bytes() + self.assertEqual( + (int(size * scale),) * 2, struct.unpack(">II", data[16:24]) + ) + data = ( + m.ROOT / f"app/src/main/res/drawable-{density}/ic_vpn_notification.png" + ).read_bytes() + self.assertEqual((int(24 * scale),) * 2, struct.unpack(">II", data[16:24])) diff --git a/scripts/update_graphics.py b/scripts/update_graphics.py new file mode 100644 index 0000000..aa93dc7 --- /dev/null +++ b/scripts/update_graphics.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +"""Generate committed Android/store/docs artwork from pinned MegaProxyIdentity SVGs.""" + +import argparse +import hashlib +import json +import re +import shutil +import struct +import subprocess +import tempfile +import xml.etree.ElementTree as ET +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +REPOSITORY = "andre487/MegaProxyIdentity" +LOCK = "assets/branding/identity-lock.json" +BADGE = "docs/assets/get-it-on-github.svg" +SOURCES = ( + "SVG/icon.svg", + "SVG/icon-radius.svg", + "SVG/icon-round.svg", + "SVG/feature-graphic.svg", + "SVG/icon-taskbar.svg", + "LICENSE", +) +DENSITIES = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4} +SVG = "http://www.w3.org/2000/svg" +ET.register_namespace("", SVG) +ET.register_namespace("xlink", "http://www.w3.org/1999/xlink") + + +def digest(content): + return hashlib.sha256(content).hexdigest() + + +def github(path): + return subprocess.run( + [ + "gh", + "api", + f"repos/{REPOSITORY}/{path}", + "-H", + "Accept: application/vnd.github.raw+json", + ], + check=True, + capture_output=True, + timeout=60, + ).stdout + + +def svg(content): + root = ET.fromstring(content) + if root.tag != f"{{{SVG}}}svg": + raise ValueError("Expected an SVG document") + for node in root.iter(): + if node.tag == f"{{{SVG}}}image": + raise ValueError("Embedded raster artwork is not a vector source") + for key, value in node.attrib.items(): + if key.rsplit("}", 1)[-1] == "href" and not value.startswith("#"): + raise ValueError("SVG sources must be self-contained") + return root + + +def adaptive_layers(content): + background, foreground = svg(content), svg(content) + for root, keep_background in ((background, True), (foreground, False)): + artwork = root.find(f"{{{SVG}}}g[@id='icon-2']") + if artwork is None or artwork.find(f"{{{SVG}}}g[@id='Background']") is None: + raise ValueError( + "Identity icon layers changed; review the adaptive icon recipe" + ) + for child in list(artwork): + if (child.get("id") == "Background") != keep_background: + artwork.remove(child) + # Keep the complete wordmark inside Android's 66dp safe circle on the 108dp layer. + x, y, width, height = map(float, foreground.get("viewBox").split()) + foreground.set( + "viewBox", + f"{x - width / 6:g} {y - height / 6:g} {width / .75:g} {height / .75:g}", + ) + return ET.tostring(background), ET.tostring(foreground) + + +def social_preview(content): + artwork = svg(content) + artwork.set("width", "1280") + artwork.set("height", "640") + artwork.set("preserveAspectRatio", "xMidYMid slice") + canvas = ET.Element(f"{{{SVG}}}svg", {"viewBox": "0 0 1280 640"}) + canvas.append(artwork) + return ET.tostring(canvas) + + +def render(content, width, height, directory): + source, output = directory / "source.svg", directory / "render.png" + source.write_bytes(content) + subprocess.run( + [ + "rsvg-convert", + "--width", + str(width), + "--height", + str(height), + "--output", + str(output), + str(source), + ], + check=True, + capture_output=True, + timeout=60, + ) + data = output.read_bytes() + if data[:8] != b"\x89PNG\r\n\x1a\n" or struct.unpack(">II", data[16:24]) != ( + width, + height, + ): + raise ValueError("Renderer returned an unexpected PNG size") + return data + + +def update(ref="main", root=ROOT): + if not re.fullmatch(r"[A-Za-z0-9._/-]+", ref): + raise ValueError("Expected an Identity commit or branch name") + if not shutil.which("rsvg-convert"): + raise RuntimeError("Install librsvg (rsvg-convert) before updating graphics") + commit = ( + ref + if re.fullmatch(r"[a-f0-9]{40}", ref) + else json.loads(github(f"commits/{ref}"))["sha"] + ) + if not re.fullmatch(r"[a-f0-9]{40}", commit): + raise ValueError("GitHub returned an invalid commit") + sources = {name: github(f"contents/{name}?ref={commit}") for name in SOURCES} + local_badge = (root / BADGE).read_bytes() + for name, content in sources.items(): + if name.endswith(".svg"): + svg(content) + svg(local_badge) + background, foreground = adaptive_layers(sources["SVG/icon.svg"]) + outputs = {"assets/branding/IDENTITY-LICENSE": sources["LICENSE"]} + with tempfile.TemporaryDirectory(prefix="megaproxy-graphics-") as temporary: + directory = Path(temporary) + + def png(path, content, width, height): + outputs[path] = render(content, width, height, directory) + + for density, scale in DENSITIES.items(): + folder = f"app/src/main/res/mipmap-{density}" + for name, source in ( + ("ic_launcher", "icon-radius"), + ("ic_launcher_round", "icon-round"), + ): + size = int(48 * scale) + png(f"{folder}/{name}.png", sources[f"SVG/{source}.svg"], size, size) + size = int(108 * scale) + png(f"{folder}/ic_launcher_background.png", background, size, size) + png(f"{folder}/ic_launcher_foreground.png", foreground, size, size) + size = int(24 * scale) + png( + f"app/src/main/res/drawable-{density}/ic_vpn_notification.png", + sources["SVG/icon-taskbar.svg"], + size, + size, + ) + icon = render(sources["SVG/icon.svg"], 512, 512, directory) + feature = render(sources["SVG/feature-graphic.svg"], 1024, 500, directory) + for language in ("en-US", "ru-RU"): + outputs[f"fastlane/metadata/android/{language}/images/icon.png"] = icon + outputs[ + f"fastlane/metadata/android/{language}/images/featureGraphic.png" + ] = feature + png( + "docs/assets/social-preview.png", + social_preview(sources["SVG/feature-graphic.svg"]), + 1280, + 640, + ) + png("docs/assets/get-it-on-github.png", local_badge, 1292, 500) + adaptive = b""" + + + + + +""" + for name in ("ic_launcher", "ic_launcher_round"): + outputs[f"app/src/main/res/mipmap-anydpi-v26/{name}.xml"] = adaptive + version = subprocess.run( + ["rsvg-convert", "--version"], + check=True, + capture_output=True, + text=True, + timeout=10, + ).stdout.strip() + lock = { + "repository": f"https://github.com/{REPOSITORY}", + "commit": commit, + "renderer": version, + "sources": {name: digest(content) for name, content in sources.items()}, + "local_sources": {BADGE: digest(local_badge)}, + "outputs": {name: digest(content) for name, content in sorted(outputs.items())}, + } + outputs[LOCK] = (json.dumps(lock, indent=2) + "\n").encode() + # Complete every download/render before replacing any committed asset. + for name, content in outputs.items(): + target = root / name + target.parent.mkdir(parents=True, exist_ok=True) + temporary = target.with_name(target.name + ".tmp") + temporary.write_bytes(content) + temporary.replace(target) + for obsolete in ( + "assets/branding/megaproxy-launcher-master.png", + "app/src/main/res/drawable/ic_vpn_notification.xml", + "app/src/main/res/values/ic_launcher_colors.xml", + ): + (root / obsolete).unlink(missing_ok=True) + print( + f"Generated {len(outputs) - 1} assets from MegaProxyIdentity {commit}. Review and commit the outputs and {LOCK}." + ) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--ref", default="main") + args = parser.parse_args() + update(args.ref) + + +if __name__ == "__main__": + main()