Compare commits

...

235 commits
0.3 ... main

Author SHA1 Message Date
w1nst0n
11f27c671c
fix: action button error when multi_user_mode is disabled
Regression introduced in 5fa6fea353
(No public release affected)
2023-04-10 22:41:13 +02:00
w1nst0n
4ef967e26c
chore: upload icomoon.io project file for UAD
This json file can be imported on https://icomoon.io/app/#/select to retrieve the UAD icons before being converted to a font
2023-04-10 17:27:12 +02:00
mmrabbani
1ef952200a
pkg: add com.google.android.apps.safetyhubAdded (#484)
== Personal Safety [Advanced] ==
Pixel app that helps you prepare and react in an emergency by quickly calling emergency services (e.g if your phone detects that you've been in a car crash, it can call for help automatically)

This app has obviously a lot of dangerous permissions due to its operation.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-04-10 15:17:43 +00:00
The_SloPro
05a1625c8f
pkg: add 71 Samsung WearOS packages (#481)
Added 71 (!) new packages from Samsung Watch5 (smartwatch)
- Recommended: 11
- Advanced: 45
- Expert: 14
- Unsafe: 1
---------

Co-authored-by: w1nst0n <w1nst0n@keemail.me>
2023-04-10 14:43:32 +00:00
Himadri Bhattacharjee
fd75b23e89
refactor: use more idiomatic expressions / DRY (#690)
- Uses short circuit logic for loading configuration file
- Only checks for windows as the target OS when running ADB shell commands since it needs the special 0x08000000 creation flag
- Parses shell output into string before checking success status since the output is used unconditionally
- Creates helper function user_flag when an additional --user argument has to be supplied to pm
- Uses unwrap_or_else when we are only concerned with applying a function to an error

---------
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-04-10 14:01:57 +00:00
Gaurav Srivastava
3699d155d8
pkg: add and update some ColorOS apps (#475)
- Added `com.heytap.market` [Recommended]
- Improved `com.coloros.oshare` description

Removing `com.coloros.oshare` will break the functionality to share photos directly from ColorOS Photos app and break the 'share with' prompt after taking a screenshot.

---
Co-authored-by: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com>
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-04-10 10:52:23 +00:00
w1nst0n
7fbec57072
fix: missing conditional compilation flag
Regression from 1df9710e1c
2023-04-10 11:45:18 +02:00
Amaan Qureshi
68092ff987
refactor: apply extra clippy lints suggestions (#673)
Cherry picking of lints from clippy::pedantic / clippy::nursery / clippy::unwrap_used / clippy::expect_used

Notable changes :
- consideration of all clippy::use-self lints
- replaced sort() by unstable_sort()
- use of map_or_else() when appropriate 
- replaced "".to_string() by String::new()
- replaced collect() + len() by count()

The code base now passes all the clippy::nursery lints

---------
Co-authored-by: w1nst0n <w1nst0n@keemail.me>
2023-04-10 09:33:12 +00:00
w1nst0n
69235a9aa5
chore: update dependencies
- Finally fixed the Dependabot alerts for a CVE not actually impacting UAD
https://rustsec.org/advisories/RUSTSEC-2020-0071
- Fixed new breaking changes from Iced

Note: UAD is not vulnerable to https://rustsec.org/advisories/RUSTSEC-2021-0145.html
2023-04-09 17:27:19 +02:00
w1nst0n
588e310882
fix: wrong recap counter
(no public release impacted)
2023-04-09 15:42:25 +02:00
w1nst0n
12c71c908a
fix: typo breaking the json file
gmrblbl
2023-04-01 18:48:29 +02:00
bestplayerbot
04ba4d6cb1
pkg: update com.samsung.android.bluelightfilter (#470)
Using the sunrise/sunset option uses the ACCESS_FINE_LOCATION permission. It's better to program the activation of the filter according to the time

Note: reducing blue light can prevent eyestrain and other health issues. See https://www.webmd.com/eye-health/blue-light-reduce-effects

---------
Co-authored-by: Seojoon You <96731476+botthebestplayerintheworld@users.noreply.github.com>
Co-authored-by: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com>
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-04-01 16:41:58 +00:00
patr1ckbat3man
a076ab525c
pkg: fix spelling mistakes and typos (#679)
no more typso!
2023-04-01 16:23:45 +00:00
AnonymousWP
2b31c66d67
pkg: update com.sec.android.app.launcher (#500)
Note: keep this package if you want swipe gestures to keep working.
Advanced --> Expert

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-04-01 16:07:00 +00:00
w1nst0n
bf12bf6a5b
fix: wrong panel displayed when no device is connected
Regression introduced in 761fb07f56
No public release affected
2023-04-01 17:03:31 +02:00
w1nst0n
fa1669b509
fix: padding in the package description panel
just a small UI change
2023-04-01 15:49:31 +02:00
Ricardo Fernández Serrata
8d302a98ec
pkg: update com.samsung.android.dynamiclock (#615)
Alternative name on some devices : Dynamic Lock

Co-authored-by: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com>
2023-03-18 19:01:14 +00:00
w1nst0n
e467fc7366
chore: update dependencies
catch up with all the breaking changes of Iced main branch
2023-03-18 19:56:20 +01:00
w1nst0n
5fa6fea353
feat: add an recap modal window (#565)
A modal window will now appear to give you a recap of the selected packages before applying the changes.

A complete overhaul of the way UAD handles package selection was required to implement this feature.
2023-03-18 18:15:31 +00:00
AnonymousWP
28642075fd
pkg: update com.samsung.android.themecenter (#654)
New name: Galaxy Themes Service

Has of lot of permissions (including INTERNET and INSTALL_PACKAGES) and connects to Samsung domains for analytics.
Pithus analysis: https://beta.pithus.org/report/973ba78ddd74a13dcf5268e980010a64ba42a3d2a1c4c62df277ead5a17cd10c

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-03-18 17:57:22 +00:00
AnonymousWP
d150bb9bea
pkg: update com.android.inputdevices (#658)
WARNING: If you are using the default Samsung keyboard, then deleting this package on some phones may cause the keyboard to completely stop working. You may get locked out of your phone if the only method to authenticate yourself is using password.

Fixes #657
2023-03-18 17:30:39 +00:00
w1nst0n
cc6406e1f8
refactor: fix new clippy lints in rust 1.68
impl Default can be derived for enums
2023-03-18 17:05:20 +01:00
w1nst0n
9f01eed2b5
fix: selection counter not resetting after pressing the refresh/reboot btn
How is it possible that no one has reported this bug in all this time? :)
2023-02-23 00:40:48 +01:00
w1nst0n
cce2503039
fix: remove null values in uad_lists
Arf! It was an old pull request. The pipeline passed but it wasn't the new pipeline with the test which would have detected this issue...

Regression from 768a1226f4
2023-02-03 00:54:15 +01:00
TubbyCat
768a1226f4
pkg: add 6 new ZTE apps (#338)
Recommended: cn.zte.recorder, com.zte.videoplayer, com.zte.privacypolicy
Advanced: com.zte.contacts.sub, com.zte.burntest.camera, com.zte.heartyservice.strategy

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2023-02-02 23:44:16 +00:00
AnonymousWP
328e1c81c7
pkg: update com.samsung.android.app.spage description (#546)
Bixby Home has been renamed to Samsung Free
2023-02-02 23:19:06 +00:00
w1nst0n
f6263d663f
refactor: simplify the package state change logic
- Improve code quality and readability
- Use `apply_pkg_state_commands()` like the backup/restore feature
- This will make easier to implement #258
2023-01-31 16:38:49 +01:00
w1nst0n
a73e0e5577
fix: package state not being updated after action
This fixes the regression introduced in 5ea6177 (no public release affected)

This commit also prevents UAD to try interacting with a protected user (to avoid error spam in the logfiles)
2023-01-23 21:29:19 +01:00
w1nst0n
749820ca86
ci: make mold the default linker on Linux runner (ci workflow)
I forgot to update this workflow too
2023-01-15 00:24:39 +01:00
w1nst0n
8bc3eee321
build: use relative mold linker path (fix #504)
Linux package managers install mold to `/usr/bin/mold`
2023-01-15 00:08:00 +01:00
w1nst0n
0e7e99a7eb
ci: make mold the default linker on Linux runner 2023-01-15 00:00:15 +01:00
w1nst0n
49712b0ad3
chg: improve backup/restore error messages
- Remove duplicated text
- Update error message when trying to restore a backup when the phone is not connected
- Add the backup name in the error message when trying to restore a deleted backup
2023-01-14 23:20:34 +01:00
w1nst0n
ae5e25377d
chg: round off the corners of the scrollbars
set border_radius to 5
2023-01-14 23:11:01 +01:00
w1nst0n
d104054074
chore: upgrade dependencies
fix breaking changes
2023-01-14 23:11:01 +01:00
AnonymousWP
b04d235edf
pkg: add com.samsung.android.galaxycontinuity [Recommended] (#464)
Samsung Flow: it's a service to 'seamlessly' connect your devices. You can authenticate your Tablet/PC with your smartphone, share content between devices, and sync notifications or view contents from your smartphone on your Tablet/PC. [...]

Has more than 81 permissions. Not the kind of app you want to keep installed if you don't use it. It increases the attack surface and can potentially compromise a lot of things (including your computer). 

FYI, 4 CVE has been discovered between 2021 and 2022: https://www.opencve.io/cve?vendor=samsung&product=samsung_flow
Pithus analysis: https://beta.pithus.org/report
/77216cd6209c73d00332180319249ac6e4ef8479e68d2a21c97a52fdc5f3d62b

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-12-18 18:59:31 +00:00
w1nst0n
e233914de0
ci: use the Rust Stable toolchain
Thanks to #458
2022-12-18 16:44:25 +01:00
nozwock
44fe67fe6e
refactor: replaces drain_filter (unstable) -with retain (stable) (#458)
`drain_filter` was the only unstable feature used in the code. You can now build UAD with Rust Stable.
 
drain_filter is not going to be stabilized anytime soon. The retain method is actually perfectly fine for our usecase. It also operate in place. The benefit of drain_filter was that the inner closure can be mutable. We don't have a need for this.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-12-18 15:41:34 +00:00
Mike Simone
967fe3144a
ci: don't use the mold linker on MacOS (#457)
The mold linker on MacOS is now commercial software : https://github.com/rui314/mold

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-12-17 22:17:36 +00:00
HamidChikh
83afd5f928
pkg: update com.samsung.android.sume.nn.service (#454)
Add package documentation (Pending -> Oem):

Provides the photo remaster feature in the Gallery app. Has the camera permission and can access all your medias but performs its job locally on the device.
https://www.samsung.com/au/support/mobile-devices/remastering-photos-on-samsung-phone/

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-12-17 21:45:35 +00:00
w1nst0n
761fb07f56
feat: warn the user when a work profile is detected (#450)
- display a warning message when switching to a work profile user
- display unavailable work profile users in the settings
2022-12-17 16:24:56 +00:00
w1nst0n
259a8540a0
pkg: update some MIUI apps + 1 new (shady) package (#446)
Add `com.miui.cleaner` (new package name of `com.miui.cleanmaster`) in the Recommended list. It's a shady app developed by Cheetah mobile which has previously been caught in ad fraud and user data theft in 2018.

Minor description update for
- cn.wps.xiaomi.abroad.lite
- com.micredit.in
- com.miui.android.fashiongallery
2022-12-07 23:16:34 +01:00
nozwock
fbb374d4c3
refactor: improve error handling (#453)
Removed many unwrap calls by propagating the error to the caller

This is the first external code contribution! Big up to @nozwock who is learning Rust ! :)

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-12-07 22:02:00 +00:00
w1nst0n
10924c74aa
chore: fix new clipy lints
Captured identifiers in format strings is really nice
https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html
2022-12-07 22:40:57 +01:00
w1nst0n
5ea61774cb
fix: UAD crash when interacting with work profiles (#448)
On recent Android devices, you can't interact with the user of the work profile. Any adb commands involving this user will fail with a `Shell does not have permission to access user ${user}.`

UAD was wrongly assuming stuff and tried to access non-existent data, thus causing a crash.
2022-11-20 01:12:04 +00:00
w1nst0n
e372c7c532
fix: restoring a backup was only affecting user 0 2022-11-20 01:21:34 +01:00
w1nst0n
dac6e5bfa8
chore: update dependencies 2022-11-19 00:24:52 +01:00
w1nst0n
2a442f97fa
feat: backup/restore the state of a device (#447)
Quick and easy way to save the state of all the system apps on a device and restore it.

This feature is available in the UAD settings.

Note: the `Export current selection` button and the non-intuitive auto import selection (see https://github.com/0x192/universal-android-debloater/issues/192) have been replaced by this new backup/restore system.
2022-11-18 00:28:00 +00:00
jamesdury
d329f341db
pkg: update com.google.android.tts (#441)
- Description update
- [Recommended -> Advanced] : removal recommendation change given the poor open-source alternatives and the usefulness of this kind of app

--------------
Default Text To Speech (TTS) engine on most of Android devices. It enables apps to convert text into voice.

Pithus analysis: https://beta.pithus.org/report/08f97758fbfae1e17c4a8e5ee1a1e6d2726ce6b0b4931d5452f2376d60f1b4fb

Note: many apps like navigation and health/sport apps rely on a TTS engine to provide speech services. Open-source TTS engines available on Android exists but, as of the end of 2022, there is only RHVoice (https://f-droid.org/en/packages/com.github.olga_yakovleva.rhvoice.android/) and it doesn't support a lot of languages. Mimic3 (https://mycroft.ai/mimic-3/) is probably the future of IoT open-source TTS engine but there is no Android support yet

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-14 22:17:03 +00:00
w1nst0n
368b9631ef
docs: update FAQ link
a link was still pointing to the old Gitlab repository
2022-11-14 21:35:49 +01:00
w1nst0n
783835bd53
chg: prevent uad_lists.json fields to be null (#296)
This will allow better unit test for the debloat list file
All fields should be mandatory and serde doesn't return any error on deserialization if an field (description, dependencies, neededBy and labels) is missing.
2022-11-14 01:31:40 +01:00
AnonymousWP
bf6ea4b8e7
pkg: fix miscellaneous typos in apps description (#440)
Did you know humans do typos spmetimes?
2022-11-14 00:05:07 +00:00
JauchenPaule
7213c8eda4
pkg: update com.huawei.android.FloatTasks description (#433)
Floating/Navigation dock is also called NaviDot

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 23:51:40 +00:00
AnonymousWP
6f01c65feb
pkg: update com.sec.android.app.safetyassurance (#428)
SOS features should not be in the Recommended list : [Recommended -> Advanced]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 23:37:53 +00:00
Charitra Agarwal
93671088ba
pkg: add com.microsoft.office.onenote [Recommended] (#424)
OneNote (https://play.google.com/store/apps/details?id=com.microsoft.office.onenote)

Microsoft and privacy you know... This app has a lot of permissions. For example it has access to phone state, including the phone number of the device, current cellular network information, the status of any ongoing calls...

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 23:12:11 +00:00
w1nst0n
940b62b479
pkg: fix typo in com.samsung.android.airtel.stubapp description
pasckage -> package

What an amazing commit!
2022-11-13 23:24:22 +01:00
Prakhar Baranwal
70f508ec84
pkg: document and fix several Samsung apps description (#391)
- Documents `com.samsung.android.privateshare` [Pending -> Oem] [Expert -> Recommended]
- Fixes the description of :
  - com.samsung.android.ipsgeofence
  - com.samsung.android.spayfw
  - com.samsung.android.allshare.service.mediashare

Co-authored-by: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com>
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 22:08:17 +00:00
Vinfall
bcca8046c4
pkg: add 15 new Vivo/iQOO apps (#307)
- Recommended: 7
- Advanced: 7
- Expert: 1

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 21:35:40 +00:00
AnonymousWP
9ec80c480f
pkg: update com.android.companiondevicemanager (#421)
WARNING: have in mind that removing this package will result in the inability to read the SD card from your computer's file manager when connecting through USB.

Recommendation change: [Advanced -> Expert]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 01:27:40 +00:00
Fahad Bin Hussain
d74f935821
pkg: fix typo in some packages description (#408)
I'm sure it's my cat's fault!
2022-11-13 01:21:40 +00:00
Rabin
074004f5ab
pkg: add com.aura.oobe.kbdi [Recommended](#405)
Persistent notification until you click on it and agree to install games. Sort of game cloud preinstalled in some Xiaomi phones. Safe to remove.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 01:17:52 +00:00
AnonymousWP
ee53de6f0a
pkg: update com.google.android.deskclock description (#390)
WARNING: on some phones, removing this makes it so alarms and notifications only vibrate and don't make any sound (via any installed app), and makes the 'Alarm' section unavailable in 'Settings > Sound & Vibration'.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 01:06:59 +00:00
AnonymousWP
f66558ab54
pkg: add 2 new Samsung apps (#380)
- com.samsung.discover [Recommended]
- com.samsung.discover.sep [Recommended]

Shows up as a pane on OneUI home and prompts you to install all sorts of random apps. OneUI works perfectly fine if you uninstall these and simply removes the discover pane.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 00:58:34 +00:00
AnonymousWP
5ada75909c
pkg: add 2 new Lenovo telemetry apps (#350)
- `com.tblenovo.center` [Recommended]
- `com.lenovo.ue.device` [Recommended]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 00:52:37 +00:00
Soul Spark
e8e0343318
docs: update manufacturer debloat list support (#363)
OK: Realme, ZTE
KO: iQOO, Vivo

Note: Vivo and iQOO list is coming soon: #307

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 00:32:44 +00:00
AnonymousWP
8c3db8f077
pkg: update description of 2 MIUI packages (#346)
- More accurate documentation of `com.miui.notification` [Advanced -> Expert]
- `com.xiaomi.miplay_client` provides indeed Miracast support

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-13 00:13:03 +00:00
Thomas
3412c42425
docs: add precision for Windows users in the How to use section (#344)
You can also launch UAD from the platform tools directory if you didn't add it to your PATH (but you should have done it)

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 23:58:06 +00:00
AnonymousWP
da3a76b6e3
pkg: update com.oplus.cosa description (#343)
Note: removing this package prevents the OnePlus Game Center to detect games.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 23:31:40 +00:00
AnonymousWP
3dd913ada5
pkg: update com.miui.miwallpaper description (#324)
Removing this package may also result in longer boot times (~15s) because the system try to call miwallpaper during boot

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 22:51:58 +00:00
w1nst0n
732b908d32
pkg: add 18 new Unihertz apps (+ more documentation) (#439)
New or changed packages :
- Unsafe: 1
- Expert: 5
- Advanced: 4
- Recommended: 10

This pull request documents `com.android.fmradio` and `com.mediatek.simprocessor` and improve documentation of several cellbroadcastreceiver related packages.

Co-authored-by: VeH-c 30818998+VeH-c@users.noreply.github.com
2022-11-12 22:41:01 +00:00
w1nst0n
01c237133d
pkg: add warnings on webview packages description
- com.android.webview
- com.oppo.webview
- com.mi.webkit.core
- com.google.android.webview

WARNING: Make to have another Webview before uninstalling it or some apps may not work properly
2022-11-12 21:51:23 +01:00
The Embraced One
8be2771933
pkg: update com.google.android.apps.messaging (#250)
WARNING: Removing this may cause issues with receiving 2FA verification text messages and calls from Google on some devices. Please let us know your experience with this on https://github.com/0x192/universal-android-debloater/pull/250 (give your phone model + Android version)

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 16:24:11 +00:00
AnonymousWP
9153dfe015
pkg: add/improve several OnePlus apps (#247)
- Document com.oneplus.diagnosemanager [Pending -> Oem] [Expert->Adanced]
- Update com.android.incallui description: it's also the OnePlus dialer app
- Add 2 new app [Expert]
- Add 1 new app [Advanced]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 16:08:04 +00:00
w1nst0n
87f3906e14
pkg: add 8 Samsung preinstalled junk (#294)
- 6 preinstalled games [Recommended]
- 1 very intrusive music app (com.pandora.android) [Recommended]
- 1 adware (com.digitalturbine.toolbar) [Recommended]
2022-11-12 14:38:12 +00:00
Hayden Plumley
5e4106f6d1
pkg: add com.oneplus.opshelf [Recommended] (#409)
Widget panel accessible from swiping down on the top-right side of the screen allowing quick access to apps, meteo, spotify, memos...

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 02:07:47 +00:00
AnonymousWP
f003025863
pkg: update com.samsung.android.forest (#431)
NOTE for Dex users: uninstalling this package makes the UI unstable, in particular the taskbar is not loaded and UI continues to crash.
2022-11-12 01:42:36 +00:00
AnonymousWP
33c782dcd9
pkg: update com.lge.ime.solution.text (#429)
- Update description
- Change recommendation: Recommended -> Advanced

WARNING: On LG G6 (and maybe on other LG phones) removing this may cause the LG keyboard to stop inputing characters. Make sure to use another keyboard before removing this package

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 01:15:10 +00:00
AnonymousWP
490065dff6
pkg: add 11 new OnePlus apps (#301)
- Add 3 new packages in the `Advanced` list
- Add 3 new packages in the `Expert` list
- Add 5 new packages in the `Recommended` list
- Update description of `com.nearme.statistics.rom` (removing it may break the search feature in the settings on some ColorOS versions)

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-12 00:57:25 +00:00
ɴιĸнιl ѕυryαvαɴѕнι
6c5a0c4498
pkg: add 28(!) Oppo/Realme apps (#359)
- 22 new packages in the `Recommended` list
- 6 new packages in the `Advanced` list
- Update description of com.coloros.weather2 and com.coloros.weather.service


Co-authored-by: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com>
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-11-11 21:47:02 +00:00
w1nst0n
befeb0e5c2
chore: fix new clippy lint 2022-11-11 19:33:09 +01:00
AnonymousWP
06c76433c5
pkg: update com.google.android.apps.wellbeing description (#415)
WARNING: It is now a hard dependency for the settings app on Android 12+ on Pixel phones. Disable this package instead of uninstalling it or the settings will crash on launch.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-10-22 18:17:33 +00:00
w1nst0n
5dc7093c5c
fix: auto reload in offline mode after remote UAD list download timeout
In offline mode, after clicking on the `no internet` button, UAD would reload with an empty list after the end of the remote UAD lists download timeout.

Regression introduced in 9b564d3 (no public release affected)
2022-10-22 16:18:14 +02:00
w1nst0n
41e437a4e9
chore: update dependencies 2022-10-22 15:51:51 +02:00
AnonymousWP
686e41281d
pkg: update com.google.android.packageinstaller [Unsafe] (#399)
Expert --> Unsafe

Google package installer. Seems to replace `com.android.packageinstaller` on newer phones. It is strangely not needed on older devices (you can still install APKs without it by using the AOSP package installer) but since Android 9, it also handles permissions control and could bootloop your device if removed.

Source: https://source.android.com/docs/core/architecture/modular-system/permissioncontroller.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-10-03 23:05:34 +00:00
Oluwamuyiwa Timothy
475da371eb
pkg: update com.samsung.android.lool (#403)
WARNING: disabling/Removing this package may remove the option to manage Power Saving, Fast Charging & Battery Protection on some devices

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-10-03 22:31:07 +00:00
Grafcube
c7db8fc78f
pkg: add com.samsung.android.setupindiaservicestnc (#393)
Recommendation list: `Recommended`

Shows a persistent notification after every system update and asks for agreement to share data with no obvious way to skip. The only way to dismiss it without agreeing to anything is to click the small text and uncheck all the items in a list. Then the 'Agree' button becomes a 'Skip' button. Uninstalling the package doesn't have any known side effects.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-09-29 21:02:50 +00:00
AnonymousWP
ea23902fa9
pkg: update com.miui.home (#326)
If you remove this package on devices based on MIUI 12+ with Android 11+, you will loose navigation gestures and recent apps view EVEN with a 3rd party launcher...

Change package recommendation: `Advanced` -> `Expert`

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-09-26 22:32:21 +00:00
w1nst0n
b2ecc0ea81
refactor: No need to use PackageRow with action_handler
- Added a new lightweight struct (CorePackage) with the minimal information needed to perform adb commands
- Added trait From<&PackageRow> for CorePackage

This change is motivated by the need to call `action_handler` for the backup/restore feature without using PackageRow.
2022-09-25 17:32:43 +02:00
w1nst0n
9b460add41
refactor(theme): use the new color!() macro of the Iced library
This macro is a sweet replacement of the hex_to_color() function
2022-09-24 14:25:17 +02:00
w1nst0n
56a96d766c
change(settings): redesign of the settings panel (#389)
Make a good UI is hard. This new look should be much better :)
2022-09-22 21:09:13 +00:00
w1nst0n
ab10511513
chore(deps): switch back to iced main branch
My pull request has been merged : https://github.com/iced-rs/iced/pull/1425
2022-09-22 01:04:16 +02:00
w1nst0n
f67e139128
refactor(theme): use enum instead of struct
This simplifies the code and enum seems more natural to use in this case. No need to use a dedicated name field when you can implement std::fmt::Display on the enum
2022-09-10 15:27:38 +02:00
w1nst0n
e094d07017
fix: device information not being logged without selecting a device
Regression introduced in 9b564d3
2022-09-07 20:06:14 +02:00
w1nst0n
b525e61a55
feat: device-specific persistent configuration (#374)
All settings are now persistent.

Some settings are device-specific which means you can maintain different settings across several devices:
- Affect all the users of the phone
- Clear and disable packages instead of uninstall them

The settings are still stored in the `config.toml` file (see https://github.com/dirs-dev/dirs-rs#example)

`0.5.x` config file is incompatible with the new specification : previous user settings will be erased
2022-09-05 08:27:47 +00:00
w1nst0n
36af0d557c
ci(release): remove automatic dev-build release feature (until v0.6)
That's because all `0.5.x` users are receiving update prompt for `dev-build`... 
I need to add an "update branch" settings and let the user choose between "beta release" and "stable release"
2022-09-04 17:34:53 +02:00
w1nst0n
646f98ee4b
pkg: improve com.xiaomi.simactivate.service description
Change initiated by #373

SIM authentication process to access exclusive features in certain MIUI applications. For the activation to work you need to send a international SMS to China.
Your carrier may block this by default and/or you'll probably need to pay extra for this.

After SIM activation, you can send text messages (Mi Messages) to other Mi users using internet connection (like i-messages).
You will be able to synchronize you messages into Mi Cloud and this also enables the Mi Find Device feature which allows you to track your phone’s location from your online Mi account.

Note: To enable/disable Mi Messages go to Settings -> System Apps -> Messaging and reboot"
2022-09-04 15:30:56 +02:00
w1nst0n
c752fe2b85
pkg: com.sec.android.app.samsungapps is required for Samsung Pay to work
`com.samsung.android.spay` has 2 direct dependencies:
- com.sec.android.app.samsungapps
- com.samsung.android.spayfw"

Closes #368
2022-09-04 15:09:24 +02:00
w1nst0n
c3524d2b4e
change(update): prevent UAD from prompting upgrade to dev-build 2022-09-03 01:30:06 +02:00
w1nst0n
bdb33fe99d
chore: fix new clippy lint 2022-09-02 14:35:00 +02:00
w1nst0n
eb1e867db2
ci(ci): remove unneeded tags-ignore directive
By default, on `push` doesn't trigger without a `tags` or `tags-ignore` directive. Actually the directive prevented the workflow to trigger at all
2022-09-02 13:51:29 +02:00
w1nst0n
77e344270b
pkg: removing com.sec.sve breaks wifi calling (VoIP)
Replaces "may break" by "will break"

Closes #143
2022-09-02 05:50:32 +02:00
w1nst0n
5d22149f7c
pkg: updatecom.sec.epdg [Advanced->Expert]
This package is tied to wifi-calling and fits better the `Expert` list.
Actually, there is no real argument to keep this in `Advanced`

Helps with #291
2022-09-02 04:42:47 +02:00
Dian Ariyanto
2e8bc6d35f
pkg: add 5 new Oppo bloatware (#164)
- com.oppo.partnerbrowsercustomizations [Recommended]
- com.redteamobile.roaming [Recommended]
- com.redteamobile.roaming.deamon [Recommended]
- com.nearme.deamon [Recommended]
- com.nearme.statistics.rom [Recommended]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-09-02 01:05:21 +00:00
w1nst0n
36e1b06470
ci(build_artifacts): Do not tarball binary for Windows
Fix a wrong copy-pasta
2022-09-01 23:57:08 +02:00
w1nst0n
9b564d3825
refactor: major code overhaul + support for the new Iced theme API (#369)
Note: UAD is now temporarily based on my fork of Iced until iced-rs/iced#1425 is merged upstream.

- Remove custom code for theming: UAD now uses the new Iced styling API
- All the code for initializing UAD (download debloat lists, fetch connected devices etc...) is now a lot more streamlined
- Other miscellaneous code improvement
- Replace row() and column() functions by the new Iced vector-like helper functions (row![], column![])
- Move navigation menu GUI code in a dedicated file
- Rework the way UAD uses events for async code
2022-09-01 21:43:45 +00:00
w1nst0n
7788f3cf83
[CI] Github Actions overhaul (+ add auto dev-build release) (#364)
- Big refactoring using the new reusable workflow feature
- Better use of caching
- Replace lld linker on MacOS/Linux by a better one: https://github.com/rui314/mold
- Automatically create a `dev-build` release whenever a commit changing the software code lands into the main branch
- Replace unmaintained Github Action `actions-rs/toolchain` by `dtolnay/rust-toolchain`
- Update all others Github Actions (I now watch all the repositories to be notified on new release)
- Use the new `automatically generated release notes` feature from Github
2022-08-28 21:19:03 +00:00
w1nst0n
20ed855533
Make sure Github Action CI triggers for json file update 2022-08-07 15:40:00 +02:00
Yohann Fabri
f47705d3b9
Fix uad_list.json typo (#336) 2022-08-07 13:06:20 +00:00
AnonymousWP
21732dae00
[Pkg] Do not break Mobile Identity Management by default (#328)
- com.android.stk2 [Recommended -> Advanced]
- com.android.stk [Recommended -> Advanced]
- com.google.android.setupwizard [Recommended -> Advanced]

Disabling/uninstalling any of these 3 packages will break mobile identity management which could be used by apps (for example your Bank) to authenticate you. See https://en.wikipedia.org/wiki/Mobile_identity_management

Closes #327

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 18:15:54 +00:00
AnonymousWP
3493718e9f
[Pkg] Improve com.motorola.launcher3 description (#332)
Provides a layout and display for app icons and listing.

WARNING: Do not remove this package if you did not switch to a 3rd-pary launcher. Keep in mind that removing this package will break the `recent apps` button (even from another launcher).

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 17:11:34 +00:00
AnonymousWP
712539c23b
[Pkg] Update com.samsung.android.app.galaxyfinder (#331)
Removing this will breaks the search in the app drawer.

Closes #329

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 16:58:44 +00:00
AnonymousWP
45b16e221e
Removal recommendation changes for 2 Samsung apps (#316)
- `com.wssyncmldm` is needed for OTA updates on Samsung phones [Recommended -> Expert]
- `com.samsung.accessibility` should not be recommended to remove [Recommended -> Expert]
2022-08-05 15:34:29 +00:00
AnonymousWP
f3a6636772
[Pkg] Update com.samsung.android.mdx.quickboard (#303)
Oddly it causes the brightness slider in the notification panel to not be displayed in landscape orientation (it's still shown in portrait)

Recommended --> Advanced

Closes #281

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 15:29:22 +00:00
AnonymousWP
11cffd75c4
[Pkg] Add com.xiaomi.macro [Recommended] (#302)
Automation task from Xiaomi like touch on MIUI Game Turbo.
Has INTERNET and READ_PHONE_STATE permission allowing access to the phone number, serial number, whether a call is active, the number that a call is connected to... What is sure (from the code) is that the app collects the IMEI.

Pithus analysis: https://beta.pithus.org/report/2b056ed84fe500552a58184035b962ba68af29457c24930c0aa8c9eba4af7bcf

Closes #282

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 15:02:49 +00:00
AnonymousWP
f8f4e7d1a4
[Pkg] Add com.aura.oobe.samsung.gl (#300)
Alternative package name of `com.aura.oobe.samsung`


Closes #265 #278

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 14:33:12 +00:00
AnonymousWP
75c80c5f15
[Pkg] Add ca.bell.wt.android.tunesappswidget [Recommended] (#299)
Home screen widget from the Bell Canada carrier which shows advertisements, promotions, news, sports & entertainment...

Closes #276 
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 14:07:37 +00:00
AnonymousWP
7ad8a152a1
[Pkg] Documentation of 2 unlisted apps (#297)
- com.android.messaging [Advanced] : Default messaging app
- com.android.screenshot [Advanced] : Default android screenshot app

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 13:58:10 +00:00
AnonymousWP
e1afa78540
[Pkg] Update com.samsung.android.android.app.dressroom (#310)
This package is needed to pick up a wallpaper. Removing this will prevent you to set a new wallpaper on Android 10+ (even from the Gallery) or changing the Material You palette on Android 12+.

`Recommended --> Advanced`

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-08-05 13:45:02 +00:00
w1nst0n
faaa6f554a
Add uad_lists.json unit test (parsing test)
All kind of errors in the `uad_lists.json` will now be detected before committing to main!
2022-07-09 04:42:40 +02:00
w1nst0n
ffcd65c893
Fix a crash when clicking on the About button in a specific situation
The crash happened if you used UAD for the first time without Internet access
2022-07-09 03:06:45 +02:00
Lennard Hofmann
88db53a17e
[Pkg] Add com.huawei.fastapp (#268)
Huawei Quick App Center: component of AppGallery (Huawei's app store) providing Quick Apps support. 

Quick Apps are Javascript+CSS apps that don't need any installation. This technology has its uses but I'm personally not a huge fan on having to rely on a JS engine to run an application.

This system app has a lot of permissions (including SEND_SMS, CAMERA, READ_EXTERNAL_STORAGE, RECORD_AUDIO... why?)

More information: https://www.xda-developers.com/huawei-quick-apps-alternative-google-instant-apps
OW2 Quick App whitepaper: https://quick-app-initiative.ow2.io/docs/Quick_App_White_Paper.pdf

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-08 22:49:07 +00:00
w1nst0n
433abb623a
Fix wrong neededBy field for com.google.android.apps.youtube.mango 2022-07-08 23:29:35 +02:00
Muhammad Rivan
fe7a622a11
[Pkg] Add 3 Android Go packages (#257)
- com.sec.android.app.sbrowser.lite [Recommended]
- com.google.android.apps.youtube.mango [Recommended]
- com.google.android.gm.lite [Recommended]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-08 21:24:58 +00:00
Qaiser
437731e958
[Pkg] Update com.miui.miwallpaper [Recommended->Advanced] (fix #242)
Removing this package might make it impossible to set a lock or home wallpaper, resulting in a black solid wallpaper.

Recommended -> Advanced
2022-07-07 23:07:27 +00:00
w1nst0n
4efd168a4b
[Pkg] Update com.sec.android.app.myfiles description (closes #266)
WARNING: If you remove this package on Android 10+, you will no longer be able to manage storage the same way as before. For example you will lose the ability to unmount or format the SD card from within the Settings app.
2022-07-06 22:37:08 +02:00
w1nst0n
7a948672bd
[Pkg] Correct com.xiaomi.scanner description (closes #293)
This is just a QR code scanner app (with a lot permissions)
2022-07-06 22:21:50 +02:00
The Embraced One
af0ed70fbc
Tell people to click on the spoiler to see the instructions (#249)
Seems like many people didn't understand you can click on the spoilers in the How to use section. The ADB installation method changes depending on your OS.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-06 20:05:57 +00:00
w1nst0n
4f2ae6c06c
Fix typo in json field name (fix #295) 2022-07-06 21:50:46 +02:00
Sidharth Shambu
773cd6b6e2
[Pkg] Add com.oplus.games to Recommended (fix #230)
OnePlus/Oppo gaming space allowing you to check game stats(such as playtime), activate game overlay features and performance settings to tweak game/battery performance during gaming.

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-06 19:42:31 +00:00
w1nst0n
a86d3852d8
[Pkg] Review of AOSP packages (closes #216)
Move many packages to `Expert` because they provide features that can be considered basic and doesn't do any harm.
2022-07-06 00:35:11 +02:00
Kevin
28f5da1778
Added OnePlus Android 12-specific packages (#217)
Added:
- com.oplus.cosa [Recommended]
- com.oplus.synergy [Advanced]
- com.heytap.accessory [Advanced]
- com.heytap.mcs [Recommended]

Useless features except maybe `com.oplus.synergy` (screencasting) 

Improved documentation: 
- com.heytap.cloud

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-05 21:31:21 +00:00
w1nst0n
b3c23ce5db
New Hotfix (v0.5.1) 2022-07-03 20:53:40 +02:00
marcingold22
fac0df1779
[Pkg] Add 5 Huawei packages + documentation updates (#155)
Added: [com.huawei.meetime, com.huawei.ohos.inputmethod, com.huawei.mycenter, com.huawei.maps.app, com.huawei.webview]

Description updates: [com.huawei.hwvoipservice, com.huawei.music, com.huawei.videoeditor]

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-03 17:48:26 +00:00
w1nst0n
4a526fe2a1
Add missing dependencies fields for several packages (#286) 2022-07-03 17:49:51 +02:00
w1nst0n
fd2a12ad49
Fix json uad_list parsing error (fix #286)
`dependencies` et `needed_by` fields should be Option<Vec<String>>
2022-07-03 15:52:54 +02:00
w1nst0n
f1b3ac40df
[Pkg] Update com.samsung.android.smartmirroring (fix #261)
- `com.samsung.android.smartmirroring` moved to `Advanced` because it is a dependency of `com.samsung.android.video`
- Add this dependency info in the description
2022-07-03 04:18:48 +02:00
w1nst0n
7f93631cf2
[Pkg] Update com.oneplus.communication.data (closes #273)
Provides the call recording feature in the OnePlus dialer
2022-07-03 04:01:32 +02:00
w1nst0n
c654f25427
[Pkg] Update com.miui.gallery [Recommended->Advanced] (fix #284)
- `Recommended` -> `Advanced` (default gallery app should not be in Recommended list)
- Add a note to tell it break the "send screenshot" feature
2022-07-03 03:42:22 +02:00
w1nst0n
5584856aec
Fix typo in release name of noseflupdate binary (fix #256)
noseflupdate -> noselfupdate
2022-07-03 02:15:40 +02:00
Simon
4261d03fd5
[Pkg] Add ColorOS weather app to Unsafe (#226)
Removing `com.coloros.weather2` and `com.coloros.weather.service` causes bootloop

Closes #158
Closes #211

Co-authored-by: Simon Demmer <simon.demmer@conti-engineering.com>
Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-07-03 00:05:33 +00:00
MCPEngu
faed75db1c
[Pkg] Fix documentation typo (#244)
Fix typo in package description :
- com.samsung.android.mdecservice
- com.samsung.android.sm.devicesecurity
2022-07-02 23:55:19 +00:00
hibby50
3ba356f9b5
[Pkg] Update com.sec.sve description (#271)
Removing this package breaks Wifi Calling

Closes #270 #270
2022-07-02 23:50:17 +00:00
w1nst0n
c85a082ef0
[Pkg] Tecno OEM support (#285)
Documentation of 30 preinstalled Tecno apps. A lot of bad apps to remove :)

Rebase of #112

Co-authored-by: Mayank-9018 <ubuntu568@gmail.com>
2022-07-02 23:40:13 +00:00
w1nst0n
e765855430
Update dependencies
- Lots of bugfixes
- 3 security fixes (nix, chrono)
2022-07-03 01:38:49 +02:00
Lakshya Rao
4f9a1d7547
[Package] [Recommended] Add com.google.android.apps.nbu.paisa.user (#206)
Google Pay, a digital wallet and payment system from Google

List of all the information collected and associated to your Google account : 
https://support.google.com/googlepay/answer/10223752?hl=en&co=GENIE.Platform%3DAndroid#zippy=%2Cinfo-that-google-may-collect

You really should not trust Google not to sell your data (even if they claim the contrary): https://venturebeat.com/2020/11/20/probeat-google-will-eventually-sell-ads-against-your-financial-data

The app itself has a LOT of permisions. See the Pithus analysis: 
https://beta.pithus.org/report/36b22c539b5f25c27a7699516c906351a25ba2daa2894eed08ae22f7a2a72c0e"

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-05-07 17:18:37 +00:00
Charitra Agarwal
aa86db344d
[Package] Fix typos in Samsung secret dialer codes (#221)
Several secret dialer codes were missing a `#`


Co-authored-by: Chiku1022 <charitraagarwal1022@gmail>
2022-05-07 13:34:52 +00:00
w1nst0n
93950f3ce0
Selection highlight + theme tweaking (closes #209)
- Highlights the package you click on
- Tweaks Lupin / Dark / Light themes to make the highlight more legible
2022-04-22 02:06:54 +02:00
w1nst0n
bd3b0699e2
[Package] Update com.google.android.ext.services (closes #194)
- Expert -> Unsafe
- Improve description

The ExtServices module updates framework components for core OS functionality such as notification ranking, autofill text-matching strategies, storage cache, package watchdog, and other services that run continually. This module is updatable, meaning it can receive updates to functionality outside of the normal Android release cycle.

Can run before the user unlocks the device (direct-boot aware) and Android 9+ version have internet and location permissions.

WARNING: Causes bootloop on most Android 11+ phones. This module is related to the Android mainline project (which is a useful project). There is no reason to mess with this.

Sources:
https://source.android.com/devices/architecture/modular-system/extservices
https://arstechnica.com/gadgets/2016/11/android-extensions-could-be-googles-plan-to-make-android-updates-suck-less/
Pithus analysis (Android 11): https://beta.pithus.org/report/e5e4a181082b88baf55e19aab0f9cb62e131d612eeaa73cddb510a52e0ff5c1a
2022-04-14 21:48:57 +02:00
w1nst0n
6a23503b7e
[Package] Update com.samsung.faceservice (closes #187)
- Improve description
2022-04-11 22:29:37 +02:00
w1nst0n
a76dda2571
[Package] Update com.sec.android.app.wlantest (closes #185)
- Improve description (thanks @giostark)
2022-04-09 19:35:25 +02:00
w1nst0n
7014c1b65f
[Package] Update com.xiaomi.account (closes #183)
- Change recommendation: `Recommended` -> `Advanced`
- Improve documentation
2022-04-09 19:30:28 +02:00
w1nst0n
d064b9493f
Fix CI/CD pipeline MacOS artifact not being a tarball 2022-04-09 18:04:49 +02:00
w1nst0n
9089123b98
Fix build issue
`retry` crate is now used in all builds
2022-04-03 02:08:43 +02:00
w1nst0n
1df2bd1abc
Update UAD screenshot in the README 2022-04-03 02:04:38 +02:00
w1nst0n
136a5bac86
New Release (v0.5) 2022-04-03 01:30:58 +02:00
w1nst0n
18cefe3e80
Better loading state management
- Dedicated state for the UadList (Downloading, Done, Failed)
- Some bugfixes introduced by previous commits (not in 0.4 release)
2022-04-03 01:11:30 +02:00
w1nst0n
077bf466d6
Use the new stateless/pure widgets from Iced
- This will be the default in Iced in the long run
- Less boilerplate code
- Simplify the separation between the data and the GUI

The `Application` is still stateful as I need to access and modify the child views. This will change one day.
2022-04-02 20:35:38 +02:00
w1nst0n
8089d6bce6
[Package] Move com.android.htmlviewer to the Expert list (#154|#169)
- Update incorrect description
- Removing this package causes a bootloop on MIUI 12.5.4+
2022-03-30 00:23:13 +02:00
w1nst0n
f166b76468
Remove useless (and broken) Gitlab CI file 2022-03-23 02:19:26 +01:00
w1nst0n
4d24549bcc
Automatically retry adb connection until a phone is connected (closes #165)
UAD now can now enter in the `FindingPhones` loading state if it hasn't found a connected device. It can happen at the launch of the app, after pressing the refresh or reboot button.

In this state, the "Finding connected devices..." message is displayed and UAD try every 500ms to initiate an ADB connection with any connected device. If after 1min there is still no device connected then UAD exit the loading state and displays en empty list of packages with the "no devices/emulators found" message.
2022-03-23 00:29:31 +01:00
w1nst0n
4e040a16ba
Add a reboot button (closes #119)
Well... there's not much more to say. It's just a reboot button.
2022-03-22 22:21:45 +01:00
Christian Stadelmann
2b0f424a45
[Package] Move Gmail to the Recommended list (#138)
According to our new recommendation guidelines (see the FAQ in the wiki), Gmail needs to move from the `Advanced` list to the `Recommended` list:
- The app is available on the Google Play Store
- Removing this app won't break any core feature of the phone nor will it cause data (emails) loss

Package(s):
- com.google.android.gm
2022-03-17 00:49:05 +00:00
w1nst0n
fb3cb732d0
Improve the How to use section
- Fix indentation
- Mention the version with the OpenGL renderer
2022-03-17 00:18:43 +00:00
KarlRamstedt
2c69392748
Huge debloat list update (#122)
- Added a bunch of new packages
- A lot of description updates and fixes
- Big revision of the recommendations according to more consistent criteria (see the wiki)

closes #98, closes #104, closes #116, closes 124, closes #136, closes #129, closes #139

Co-authored-by: w1nst0n <55300518+0x192@users.noreply.github.com>
2022-02-19 17:43:36 +00:00
w1nst0n
0993c51c7e
Add a no-self-update build for MacOS and Linux
Useful if UAD is distributed into repositories. The update process will then be managed by a package manager.

Note: MacOS builds are will now also be released as a compressed tarball (like for Linux)
2022-02-11 21:26:45 +00:00
w1nst0n
1b4516177e
Update issue templates (fix #127)
- Remove duplicate template
- Better description for the `Bug report` template
2022-02-11 20:39:06 +00:00
w1nst0n
ee557b2624
Fix crash on start when no device is connected
This is a regression from 8424ea4
2022-02-10 02:24:09 +01:00
w1nst0n
4659eb7b0f
Add a self-update feature (#121)
- Add an update button in the About page which links to the Github Releases page
- Display if there is a new version available in the About page
- Display an error if the request to Github failed
- Download the new UAD version and update the software (the hard part)
- Display an update button instead of the `Apps` button if there is an update available
- Add a `no-self-update` cargo feature for this (we don't want auto-update if a package manager already handles this)

The request is done asynchronously, each time you launch UAD or press the About button
The message checking updates... is displayed in the meantime

Update process:
- Download the right release based on the current file name (temp_<release_name>)
- Extract the executable from the tarball for Linux and MacOS release
- Rename the current executable to a temp path and rename the new version as the original file name
- Spawn a new process to run the new (renamed) executable and kill the current one
- Remove the old executable

Acknowledgement:
@tarkah for the inspiration of the self-update code (https://github.com/ajour/ajour/pull/222)
2022-02-09 23:27:13 +00:00
0x192
8424ea431e
Initialization logic refactor + fix concurrency issues
Parallelizing tasks with Iced Commands can be tricky because there is no `join` feature.

This commit simplifies all the init logic (download UAD list, get devices info, load packages) and fix a concurrency issue where
a tasks could be starting when it should have waited for another task to finish.

Add a state for the `List` module: `Ready` or `Loading(LoadingState)`
2022-02-08 16:06:53 +01:00
0x192
b31568b277
[Package] Move Google keyboard to Advanced list (#120)
Default keyboards should not be in the `Recommended` list
- com.google.android.apps.inputmethod.hindi [Recommended -> Advanced]
- com.google.android.inputmethod.latin [Recommended -> Advanced]
2022-02-05 20:52:43 +01:00
w1nst0n
c604d86466
Button to unselect all the packages (closes #68) (#117)
- Add a `unselect all` button
- Minor UI improvement: the search/filter row now has the same width as the packages row

Note that this only selects/unselects the current filtered packages.
For example if you have the filter `Recommended` it will only affect the packages in this list.
2022-02-04 16:30:27 +00:00
0x192
28e97e6a0d
[Package] Add com.tblenovo.lenovotips [Recommended] (close #115)
Useless Lenovo Tips app used by Lenovo to display un-dismissable and un-mutable ads in notifications
Source: https://news.ycombinator.com/item?id=28382081
2022-02-03 23:49:55 +01:00
w1nst0n
b8fd936de2
Download uad_list.json from Github (#110)
- Try to download the list from Github when you launch UAD
  - If it succeeds, cache the file
  - Use the cache file if no Internet or fallback to the embedded list
  - Display a special waiting message during the download

- You can skip the waiting screen if no internet. It will fallback to the cached or the embedded list
- Display the documentation version (date) in the About page
- Add an update button in the About page (with time last updated)
2022-02-01 18:18:58 +00:00
William Theaker
af3bb6d779
Fix minor typing errors in the README (#109)
- Typos
- Misspellings
2022-01-31 21:35:13 +00:00
0x192
083eb42f84
Update features listed in the README 2022-01-31 01:47:23 +01:00
0x192
e4cdf9f205
Update screenshot
Forgot to showcase the selection feature
2022-01-31 01:46:45 +01:00
0x192
e89c9ae4a3
New Hotfix (v0.4.1) 2022-01-31 01:06:41 +01:00
0x192
64c99db00d
Fix selection counter never decreasing 2022-01-31 01:06:14 +01:00
0x192
ed4b753bfd
Update the screenshot
Replace v0.3 screenshot by v0.4 screenshot
2022-01-31 00:49:01 +01:00
Adam Elaoumari
bf45971239
Fixed typo for France carriers (#106)
- Bouyges -> Bouygues
- Removed Sosh because it's only a brand of the Orange carrier
2022-01-30 23:10:32 +00:00
0x192
49a38c8da8
New Release (v0.4) 2022-01-30 16:16:24 +01:00
0x192
2ac4a43dd8
Fix bug in open_url() 2022-01-30 16:16:10 +01:00
0x192
280447c0ea
Remove unused ADB binaries 2022-01-29 23:52:54 +01:00
0x192
9407eaefce
Legacy build is no longer needed
Iced now support those old hardware
2022-01-29 23:20:40 +01:00
0x192
f70a7a6062
Fix bug breaking multi-devices support
There are Unicode whitespaces in [[:ascii:]]. Whoops
2022-01-29 23:00:30 +01:00
0x192
c27f355191
Update dependencies & fix breaking changes 2022-01-29 22:31:04 +01:00
0x192
00168a8b0f
[Package] Add 2 Xiaomi apps
- com.mi.globalbrowser [Recommended]
- com.xiaomi.calendar [Recommended]

Since Android 12 you can no longer uninstall them but you still can disable them.
2022-01-29 22:22:40 +01:00
0x192
c638efe1f0
Fix new clippy warning 2022-01-29 22:14:08 +01:00
0x192
577fdff474
Disable mode is no longer available before Android 6.0
- Greyed out `disable mode` setting
- `Not available` button redirecting to the corresponding FAQ entry
2022-01-29 22:11:58 +01:00
0x192
17f025f020
Review of the ADB commands (fix #78)
- Big refactor of action_handler()
- pm block/unblock is now used on Android 4.4
- pm hide/unhide is now used on Android 5.x

`pm disable` or `pm disable-user` doesn't work without root on those older devices.
2022-01-29 21:36:07 +01:00
0x192
6a2de27d42
Minor light theme update
More readable color
2022-01-18 23:31:55 +01:00
0x192
cc2455b9f2
Forgot to run rust fmt
I really should add a pre-commit hook for this
2022-01-18 04:20:41 +01:00
0x192
c188479f88
Fix critical bug preventing any action with older devices (< Android 8.0)
I don't know what happened. This is a nasty bug!
(and there is actually 2 bugs in the if condition)

It will help #78
2022-01-18 03:43:39 +01:00
0x192
d840934dba
Check if the ADB command succeed before changing the package state
`perform_commands()` now returns a result.

On old devices, ADB commands can return the '0' exit code even if there is an error
On Android 4.4, ADB doesn't check if the package exists. It does not return any error if
you try to `pm block` a non-existent package.

When the command doesn't exist ADB write to stdout "Error: ..." so we can at least catch this.
2022-01-18 02:56:36 +01:00
0x192
cbae57bca5
Only the first action should change the package state
Regression from 608c05a
Make sure to only send `Message::ChangePackageState` for the first action
2022-01-18 02:08:18 +01:00
0x192
72c7bbdd28
Update the about page
- Update the text
- Add 3 buttons which redirect to Github/Wiki/Issues
- Add a button to redirect to the logfiles directory
2022-01-11 16:31:57 +01:00
0x192
ed246bf9bf
Much more sensible logging implementation
- Use of dirs::cache_dir for logfiles location
- Logfile name convention is now "UAD_%Y%m%d.log"
- Logger now only appends (no more overwrite)
- Logger now creates a new logfile each day
- Creation of a `uad_dir()` helper function to access directories UAD uses
2022-01-09 18:59:11 +01:00
Robin Rapp
04f9dd17f5
[Package] Add apps from Fairphone phone + documentation updates (#92)
- Add 3 Fairphone packages
- Add 7 Qualcomm packages
- Update documentation of 6 Qualcomm packages
2022-01-08 19:38:15 +00:00
VeH-c
0bb93c1fae
[Package] Add a few apps from Unihertz manufacturer (#87)
com.agui.studentmodel
com.agui.game
2022-01-08 18:55:49 +00:00
0x192
18cbba2b13
Refactor workflow and use tarballs for linux builds
- Better usage of the matrix strategy
- Linux builds are now released as a tarball (.tar.gz)
2022-01-08 19:42:36 +01:00
0x192
eda0882451
Update dependencies
Notables updates:
- wgpu v0.11.1 -> v0.12.0
- winit v0.20.0 -> v0.26.0
2022-01-08 19:37:37 +01:00
0x192
608c05a408
Asynchronous + multi-threaded ADB commands (closes #65) 🚀
Smarter use of iced::Command to run as many tasks as possible concurrently/in parallel

- ADB commands no longer block the interaction with the UI.
- The UI now reacts in real-time to ADB actions (no longer waits for all actions to finish)
- Some refactoring along the way
2021-12-20 13:59:19 +01:00
0x192
c93e722d27
Add a length check in update_selection_count()
The selection should always be positive
2021-12-20 02:42:39 +01:00
0x192
72ba0ab02b
Settings checkboxes have their own style
The default text color field recently added in Iced is set to Color::Black which is not suitable for UAD
2021-12-20 02:38:56 +01:00
0x192
8831628015
Update dependencies [Iced breaking change]
Fix Iced breaking change: checkbox text_color styling
2021-12-10 16:03:51 +01:00
w1nst0n
c5e45d0053 More granular templating system
- Use Bug report template only for bugs related to UAD itself
- Add a template for new packages additions
- Add a template for documentation updates
- Add a template for all breakages related to debloating
2021-12-10 11:40:29 +00:00
w1nst0n
39da91a33d Update issue templates
- Add Feature request template
- Update Bug report description
2021-12-01 17:08:43 +00:00
w1nst0n
c789cef26f Add a bug issue template 2021-12-01 15:40:04 +00:00
0x192
855df3458c
[Package] Update com.android.vending
- Correct wrong description
- Put the Expert tag
2021-12-01 16:19:10 +01:00
Jonas
68e00fdafc
[Package] Update com.android.thememanager(.module) (#76)
Updates the description of thememanager and thememanager.module.
Moves thememanager.module to Recommended list.

Co-authored-by: Jonas Ott <mail@jonas-ott.de>
2021-12-01 15:04:30 +00:00
0x192
b5f1132929
[Package] Update many themes/icon packs (#75)
- Move some packages from Pending to Aosp list
- Most of those packages now have the `Advanced` tag
- Improve/Correct some descriptions
2021-12-01 15:46:13 +01:00
0x192
069e14cdc3
[Package] Update com.miui.guardprovider description (#74)
Clearer warning message: removing this packages break any app installation/update
2021-12-01 14:40:54 +01:00
0x192
c087dec8bb
[Package] Update com.samsung.android.honeyboard description (#64)
Removing this packages breaks the Accessibility settings on Android 11
2021-11-13 02:11:14 +01:00
0x192
a129de5085
[Package] Be more careful with the default keyboard packages
Default keyboard packages shouldn't be in the Recommended list. 
This commit moves most of them to the Expert list and update some descriptions to remind to have another keyboard before removing the package.
2021-11-13 02:10:20 +01:00
Felix
6f4ccd752d
Update com.huawei.android.thememanager (#62)
I tried removing the package, and I found the following:
1. You can no longer set any wallpapers by pinching on the home screen and tapping "Wallpaper", the button just does nothing.
2. There are changes in the Settings under "Home screen & wallpaper":
    2.1. Tapping "Themes" will do nothing.
    2.2. The symbol/picture next to "Wallpaper" vanishes.
    2.3. "Wallpaper" reverts to working like on stock Android (or whatever is on a Moto G6): There's a list of sources, in my case Huawei's "Gallery", "Live wallpapers" and Google's "Photos", which allow you to choose a picture for your wallpaper. Interestingly, you can still blur and toggle scrollability in what seems to be Huawei UI when choosing "Gallery", "Photos" leads to a different UI.

I adjusted the description. Hope this helps!
2021-11-07 19:37:03 +00:00
0x192
173be4df5c
[Package] Update com.huawei.parentcontrol description (#51) 2021-11-02 21:59:56 +01:00
0x192
2b97541a21
Allow breaking changes for all unstable dependencies 2021-11-02 21:45:20 +01:00
0x192
b03260a684
[Feature] Persistent settings (#44 / fix #50) 2021-11-02 21:14:36 +01:00
0x192
28647f38cb
[Package] Add android (unsafe) (#58) 2021-10-23 15:18:29 +02:00
0x192
77915446ce
Fix emulators not being detected 2021-10-23 14:29:34 +02:00
0x192
5a14ab8dc3
Adress brand new clippy warnings 2021-10-23 03:10:29 +02:00
0x192
edf7dd6c47
Fix crash when no device is connected 2021-10-23 02:59:47 +02:00
0x192
18c9499e93
Add a manual build/upload artifacts workflow 2021-10-23 01:04:51 +02:00
0x192
b26f6b60de
Add .log files to .gitignore 2021-10-22 23:49:02 +02:00
0x192
52cd522732
Fix code formatting (rsmft) 2021-10-22 23:40:12 +02:00
0x192
e49df41534
Feature/Multi-device support (#49) 2021-10-22 23:35:59 +02:00
0x192
1dc0c02f51
Update dependencies 2021-10-21 16:02:54 +02:00
0x192
4a60606711
No longer use of MinGW toolchain (fix #57) 2021-10-21 15:29:30 +02:00
0x192
af4d35fe57
[Package] Add uk.co.ee.myee (#52) 2021-10-15 19:48:45 +02:00
0x192
98d511fadf
Review of the recommendations (#41) 2021-10-15 18:03:42 +02:00
0x192
71e05e3434
Fix typo (adress #48) 2021-10-11 19:03:28 +02:00
karim-attia
acb99b2120 Update README.md
typo: developpement -> development
2021-10-11 16:56:55 +00:00
0x192
69c3d8beb9
Ok. Last screenshot change 2021-10-11 00:35:21 +02:00
0x192
de7871a525
Even better screenshot 2021-10-11 00:18:13 +02:00
0x192
e87d0e362e
Better screenshot 2021-10-10 21:54:20 +02:00
0x192
35c9f25eaf
Fix screenshot extension issue 2021-10-10 21:41:33 +02:00
47 changed files with 26394 additions and 10282 deletions

View file

@ -1,12 +1,6 @@
[target.x86_64-pc-windows-gnu]
linker = "/usr/bin/x86_64-w64-mingw32-gcc"
ar = "/usr/bin/x86_64-w64-mingw32-ar"
[target.i686-pc-windows-gnu]
linker = "/usr/bin/i686-w64-mingw32-gcc"
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

View file

@ -0,0 +1,34 @@
---
name: Add new package(s)
about: You want to add new apps in the debloat list
title: ""
labels: package::addition
assignees: ""
---
**Your phone model:**
**Packages:**
```
com.this.is.a.bad.application
com.this.is.another.bad.application
...
```
- [ ] **I removed all those packages on my phone**
If not why. Leave the brackets blank and explain why.
## Document each package the best you can
**List**: `Google`|`Misc`|`OEM` (manufacturer)|`AOSP`|`Pending`|`Carrier` (isp).
**Removal**: `Recommended`, `Advanced`, `Expert` (this can break important features),
or `Unsafe` (this can bootloop the phone or break extremely important features).
### \<package name\>
**List**: \<list\>
**Removal**: \<recommendation list\>
> Description. Link to its Playstore page if it exists.

19
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,19 @@
---
name: Bug report
about: You have an issue with the UAD software itself
title: ""
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**You have a solution?**
What to do to fix the issue.
**UAD log**
Upload the logfile generated by UAD.

View file

@ -0,0 +1,18 @@
---
name: Debloat issue report
about: Your phone has unexpected issues after debloating
title: ""
labels: package::breakage
assignees: ""
---
**Your phone model**:
**Describe the issue**
A clear and concise description of what the problem is.
**You have a solution?**
What to do to fix the issue.
**UAD log**
Upload the logfile generated by UAD. It would be difficult to help you without it.

View file

@ -0,0 +1,10 @@
---
name: Feature request
about: You want a new feature
title: ""
labels: enhancement
assignees: ""
---
**Describe the feature you want**
A clear description of what you want to happen.

View file

@ -0,0 +1,37 @@
---
name: Update apps description or recommendation
about: You want to improve/update a description/recommendation
title: ""
labels: package::documentation
assignees: ""
---
**Your phone model**:
**Packages documentation to update:**
```
com.this.is.a.application
com.this.is.another.application
...
```
## Documentation Change
**List**: `Google`|`Misc`|`OEM` (manufacturer)|`AOSP`|`Pending`|`Carrier` (isp).
**Removal**: `Recommended`, `Advanced`, `Expert` (this can break important features),
or `Unsafe` (this can bootloop the phone or break extremely important features).
### \<package name\>
**List**: \<current list\> :arrow_right: \<proposed list\>
**Removal**: \<current recommendation list\>
:arrow_right: \<proposed recommendation list\>
### Current description
> Current description
### Proposed description
> Proposed description

17
.github/release.yml vendored Normal file
View file

@ -0,0 +1,17 @@
changelog:
exclude:
labels:
- package::documentation
- package::addition
- package::breakage
- package::bootloop
categories:
- title: Added
labels:
- feature
- title: Changed
labels:
- enhancement
- title: Fixed
labels:
- bug

View file

@ -1,65 +0,0 @@
name: Build
on:
push:
tags:
- '*'
jobs:
publish:
name: Building ${{ matrix.build_target }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
build_target: [linux, macos, windows]
include:
- build_target: linux
os: ubuntu-latest
- build_target: macos
os: macos-latest
- build_target: windows
os: windows-latest
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
target: ${{ matrix.target }}
profile: minimal
- name: Install Linux dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get -qq update
sudo apt-get install -y libxkbcommon-dev lld
- uses: actions/checkout@v2
- name: Cargo build (WGPU feature)
uses: actions-rs/cargo@v1
with:
command: build
args: --release
- name: Renaming
if: matrix.os == 'windows-latest'
run: mv target/release/uad_gui.exe target/release/uad_gui-${{ matrix.build_target }}.exe
- name: Renaming
if: matrix.os != 'windows-latest'
run: mv target/release/uad_gui target/release/uad_gui-${{ matrix.build_target }}
- name: Cargo build (Glow feature)
uses: actions-rs/cargo@v1
with:
command: build
args: --release --features glow
- name: Renaming
if: matrix.os == 'windows-latest'
run: mv target/release/uad_gui.exe target/release/uad_gui-${{ matrix.build_target }}-opengl.exe
- name: Renaming
if: matrix.os != 'windows-latest'
run: mv target/release/uad_gui target/release/uad_gui-${{ matrix.build_target }}-opengl
- name: Create pre-release
uses: softprops/action-gh-release@v1
with:
body_path: ${{ github.workspace }}/CHANGELOG.md
files: target/release/uad_gui-*
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

65
.github/workflows/build_artifacts.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: Build artifacts
on:
workflow_dispatch:
workflow_call:
jobs:
build:
name: Building ${{ matrix.build_target }} [${{ matrix.graphics }}] [${{ matrix.update_feature }}]
runs-on: ${{ matrix.os }}
strategy:
matrix:
build_target: [linux, macos, windows]
graphics: [glow, wgpu]
update_feature: [self-update, no-self-update]
exclude:
- build_target: windows
update_feature: no-self-update
include:
- build_target: linux
os: ubuntu-latest
- build_target: macos
os: macos-latest
- build_target: windows
os: windows-latest
- graphics: glow
renderer: "-opengl"
- graphics: wgpu
renderer: "" # Vulkan but we don't want this in the binary filename
- update_feature: self-update
update_name: "" # we don't want this in the binary filename
- update_feature: no-self-update
update_name: "-noseflupdate"
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: rui314/setup-mold@v1 # faster linker
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target
key: ${{ runner.os }}-release-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.OS }}-release-
if: matrix.os == 'ubuntu-latest'
- name: Building
run: cargo build --release --no-default-features --features ${{ matrix.graphics }},${{ matrix.update_feature }}
- name: Creating ./bin directory
run: mkdir -p bin
- name: Renaming binaries [Windows]
if: matrix.os == 'windows-latest'
run: mv target/release/uad_gui.exe bin/uad_gui-${{ matrix.build_target }}${{ matrix.renderer }}.exe
- name: Renaming binaries [Others]
if: matrix.os != 'windows-latest'
run: mv target/release/uad_gui bin/uad_gui${{ matrix.update_name }}-${{ matrix.build_target }}${{ matrix.renderer }}
- name: Tarball Linux/MacOS binary
if: matrix.os != 'windows-latest'
run: tar -czf bin/uad_gui${{ matrix.update_name }}-${{ matrix.build_target }}${{ matrix.renderer }}{.tar.gz,}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: uad_gui${{ matrix.update_name }}-${{ matrix.build_target }}${{ matrix.renderer }}
path: bin/uad_gui-*

View file

@ -2,106 +2,56 @@ name: Continuous Integration
on:
push:
paths:
- '**.rs'
- 'Cargo.lock'
- 'Cargo.toml'
- "**.rs"
- "Cargo.lock"
- "Cargo.toml"
- "**.json"
pull_request:
paths:
- '**.rs'
- 'Cargo.lock'
- 'Cargo.toml'
- "**.rs"
- "Cargo.lock"
- "Cargo.toml"
- "**.json"
jobs:
check:
name: Check
runs-on: ubuntu-latest
lint:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
lint: [check, test, clippy, fmt]
exclude: # https://github.com/community/community/discussions/7835
- os: windows-latest
lint: clippy
- os: windows-latest
lint: fmt
- os: macOS-latest
lint: clippy
- os: macOS-latest
lint: fmt
include:
- lint: check
args: " --all-features"
- lint: test
args: ""
- lint: clippy
args: " --all --all-features -- -D warnings"
- lint: fmt
args: " --all -- --check"
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt -qq update
sudo apt install -y lld
- uses: actions/cache@v2
- uses: actions/checkout@v3
- uses: rui314/setup-mold@v1 # faster linker
- uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target
key: ${{ runner.os }}-check-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
key: ${{ runner.os }}-${{ matrix.lint }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.OS }}-${{ matrix.lint }}-
- uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: nightly
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt -qq update
sudo apt install -y libxkbcommon-dev lld
- uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- uses: actions-rs/cargo@v1
with:
command: test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt -qq update
sudo apt install -y lld
- uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-clippy-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all -- -D warnings
components: clippy,rustfmt
- run: cargo ${{ matrix.lint }}${{ matrix.args }}

50
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,50 @@
name: Build and release
on:
push:
branches:
- main
paths:
- "**.rs"
- "Cargo.lock"
- "Cargo.toml"
tags-ignore:
- dev-build
env:
dev_tag: dev-build
jobs:
build:
uses: ./.github/workflows/build_artifacts.yml
release:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Downloads artifacts
uses: actions/download-artifact@v3
with:
path: bin
- name: Create pre-release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
body_path: ${{ github.workspace }}/CHANGELOG.md
files: bin/*/uad_gui-*
prerelease: true
# - name: Update dev-build tag
# if: ${{ github.event_name == 'push' }}
# run: |
# git tag -d ${{ env.dev_tag }} || true
# git push origin :refs/tags/${{ env.dev_tag }} || true
# git tag ${{ env.dev_tag }}
# git push origin ${{ env.dev_tag }}
# - name: Create dev-build release
# if: ${{ github.event_name == 'push' }}
# uses: softprops/action-gh-release@v1
# with:
# generate_release_notes: true
# files: bin/*/uad_gui-*
# prerelease: true
# tag_name: ${{ env.dev_tag }}

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
debug/
target/
*.log

View file

@ -1,57 +0,0 @@
image: rustlang/rust:nightly-bullseye
default:
before_script:
- rustc --version
- cargo --version
stages:
- build
i686-pc-windows-gnu:
stage: build
before_script:
- apt update -yqq && apt install -yqq mingw-w64
- rustup target add i686-pc-windows-gnu
script:
- cargo build --features glow --release --target i686-pc-windows-gnu
- mv target/i686-pc-windows-gnu/release/uad_gui.exe target/i686-pc-windows-gnu/release/uad_gui-opengl.exe
artifacts:
paths:
- target/i686-pc-windows-gnu/release/uad_gui-opengl.exe
x86_64-pc-windows-gnu:
stage: build
before_script:
- apt update -yqq && apt install -yqq mingw-w64
- rustup target add x86_64-pc-windows-gnu
script:
- cargo build --features glow --release --target x86_64-pc-windows-gnu
- mv target/x86_64-pc-windows-gnu/release/uad_gui.exe target/x86_64-pc-windows-gnu/release/uad_gui-opengl.exe
- cargo build --features wgpu --release --target x86_64-pc-windows-gnu
artifacts:
paths:
- target/x86_64-pc-windows-gnu/release/uad_gui.exe
- target/x86_64-pc-windows-gnu/release/uad_gui-opengl.exe
x86_64-unknown-linux-gnu:
stage: build
before_script:
- apt update -yqq && apt install -yqq libxkbcommon-dev lld
script:
- cargo build --features glow --release --target x86_64-unknown-linux-gnu
- mv target/x86_64-unknown-linux-gnu/release/uad_gui target/x86_64-unknown-linux-gnu/release/uad_gui-opengl
- cargo build --features wgpu --release --target x86_64-unknown-linux-gnu
artifacts:
paths:
- target/x86_64-unknown-linux-gnu/release/uad_gui
- target/x86_64-unknown-linux-gnu/release/uad_gui-opengl

View file

@ -5,60 +5,166 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The sections should follow the order `Packaging`, `Added`, `Changed`, `Fixed`
The sections should follow the order `Apps`, `Added`, `Changed`, `Fixed`, `Packaging`
and `Removed`.
## [Unreleased]
## [0.6] - Unreleased
**WARNING: Settings specification has changed. Previous user settings will be erased**.
### Added
- [[#374](https://github.com/0x192/universal-android-debloater/pull/374)] **Device-specific persistent configuration:** Some settings are now device-specific which means you can maintain different settings across several devices.
- [[#447](https://github.com/0x192/universal-android-debloater/pull/447)] **Backup/Restore the state of a device:** Quick and easy way to save the state of all the system apps on a device and restore it.
- [[#450](https://github.com/0x192/universal-android-debloater/pull/450)] **Warn the user when a work profile is detected:** Displays a warning message when switching to a work profile user and displays unavailable work profile users in the settings.
### Changed
- [[#374](https://github.com/0x192/universal-android-debloater/pull/374)] ALL settings are now persistent.
### Fixed
- [[#448](https://github.com/0x192/universal-android-debloater/pull/448)] UAD crash when interacting with work profiles on recent phones.
### Removed
- The `Export current selection` button and the unintuitive auto import selection (see https://github.com/0x192/universal-android-debloater/issues/192) have been replaced by the new backup/restore system.
## [0.5.1] - 2022-07-03
Since `0.5.0`, all changes related to apps are available to users without downloading a new version of UAD as the software directly download the json debloat list from Github. These changes can be tracked in commits with `[Pkg]` in their name. [See the commits](https://github.com/0x192/universal-android-debloater/commits/main)
### Added
- [[#209](https://github.com/0x192/universal-android-debloater/issues/209)] Persistent highlighting when you click on a package
### Changed
- `neededBy` and `dependencies` field can now list multiple packages (feature not visible in the UI yet)
### Fixed
- [[#286](https://github.com/0x192/universal-android-debloater/issues/286)] UAD stuck on "Downloading UAD lists. Please wait" screen
## Packaging
- [[#256](https://github.com/0x192/universal-android-debloater/issues/256)] Fixed typo in the release name of the noseflupdate variation
- Bump dependencies
## [0.5.0] - 2022-04-03
### Apps
- [[#115](https://github.com/0x192/universal-android-debloater/issues/115)] Added `com.tblenovo.lenovotips` to the recommended list.
- [[#120](https://github.com/0x192/universal-android-debloater/pull/120)] Move Google keyboard to `Advanced` list (Default keyboards should not be in the `Recommended` list)
- [[#169](https://github.com/0x192/universal-android-debloater/issues/154) Move `com.android.htmlviewer` to the `Expert` list. Removing it bootloop the device on MIUI 12.5.4+.
Huge thanks to [@KarlRamstedt](https://github.com/KarlRamstedt) for their help in this major debloat list update:
- [[#122](https://github.com/0x192/universal-android-debloater/pull/122)] Added a bunch of new packages
- [[#122](https://github.com/0x192/universal-android-debloater/pull/122)] A lot of description updates and fixes
- [[#122](https://github.com/0x192/universal-android-debloater/pull/122) | [#138](https://github.com/0x192/universal-android-debloater/pull/138)] Big revision of the recommendations according to more consistent criteria ([see the wiki](https://github.com/0x192/universal-android-debloater/wiki/FAQ#how-are-the-recommendations-chosen))
### Added
- [[#68](https://github.com/0x192/universal-android-debloater/issue/68)] **Unselect all button**: Let's you unselect all the packages you see on screen (i.e in the current filtered list).
- [[#119](https://github.com/0x192/universal-android-debloater/issue/119)] **Reboot button**: Let's you quickly reboot the currently selected device.
- [[#110](https://github.com/0x192/universal-android-debloater/pull/110)] **Remote `uad_lists.json` download**: The debloat list is now directly fetched from the main branch of this repo when you launch UAD. This means there is no longer the need to release a new version of UAD for updating the debloat lists! :rocket:
- [[#121](https://github.com/0x192/universal-android-debloater/pull/121)] :arrows_counterclockwise: **UAD self-update**: UAD will now check at launch if there is a new version of itself and enable you to perform the update directly from the app! :rocket:
### Changed
- [[#165](https://github.com/0x192/universal-android-debloater/issues/165)] UAD now tries every 500ms (for 1min) to initiate an ADB connection until a device is found during the `FindingPhones` loading state.
- All the init process was reworked and a status message is displayed at each stage (`DownloadingList`, `FindingPhones`,`LoadingPackages`,`UpdatingUad` `Ready`) so you know what is happening.
- Minor UI changes
### Packaging
- Add a `no-self-update` build for MacOS and Linux. Useful if UAD is distributed into repositories. The update process will then be managed by a package manager.
- MacOS builds are now also be released as a compressed tarball (like for Linux). You won't need to manually add the executable permission anymore. ([more info](https://github.com/actions/upload-artifact/issues/38))
- Bump dependencies
## [0.4.1] - 2022-01-31
### Fixed
- Selection counter never decreasing.
## [0.4] - 2022-01-30
### Apps
- [[#92](https://github.com/0x192/universal-android-debloater/pull/92)] Added 3 Fairphone packages + 7 Qualcomm packages (thanks [@VeH-c](https://github.com/VeH-c))
- [[#87](https://github.com/0x192/universal-android-debloater/pull/87)] Added 2 Unihertz packages (thanks [@rar0ch](https://github.com/rar0ch))
- [[#52](https://github.com/0x192/universal-android-debloater/issues/52)] Added `uk.co.ee.myee` to the debloat lists (thanks [@lawson58](https://github.com/lawson85)).
- [[#58](https://github.com/0x192/universal-android-debloater/issues/52)] Added `android` to the debloat lists with the tag `Unsafe`.
- Added 2 new Xiaomi packages to the `Recommended` list.
- Multiple package description improvement (thanks [@jonas-ott](https://github.com/jonas-ott) and [@felurx](https://github.com/felurx) for the help)
- Review of the package lists recommendations. The `Recommended` debloat list is now safer (less likely to remove something you'd want to keep).
### Added
- [[#49](https://github.com/0x192/universal-android-debloater/issues/49)] Multi-device support: You are now able to select a device among the list of all ADB connected devices/emulators.
- [[#44](https://github.com/0x192/universal-android-debloater/issues/44)] Persistent settings: Settings (only `theme` for now) are saved to a config file. Its location follows [the standards of the different OS](https://github.com/dirs-dev/dirs-rs#example).
- Links to the Github page, wiki, github issues and logfiles in the `About` page.
### Changed
- [[#65](https://github.com/0x192/universal-android-debloater/issues/65)] ADB commands now run in parallel and asynchronously! This means no more UI freeze when performing long/many actions! :rocket:
- UI now updates itself in real time when performing ADB actions (thanks to async & multithreading). Before, it waited for the end of all actions.
- Logfiles are now located in a more conventional place: [cache_dir](https://docs.rs/dirs/latest/dirs/).
- Previous logs are no longer overwritten. The logger now only appends to the current logfile of the day (UAD_%Y%m%d.log).
- Each new day the logger will create a new file on UAD launch.
- [[#78](https://github.com/0x192/universal-android-debloater/issues/78)] Disable mode is now only available on Android 6+ because the disable ADB commands do not work without root on older devices. The setting will be greyed-out for those devices.
- Minor light theme update
### Fixed
- [[#50](https://github.com/0x192/universal-android-debloater/issues/50)] Resync button flipping theme back to `Lupin`.
- [Regression ([048e7f](https://github.com/0x192/universal-android-debloater/commit/048e7fc8fd6d44b0e8ba933c289249366254a9cc))] Weird disabled/greyed action button with older devices (< Android 8.0). Package could be selected but no action was performed.
- [[#78](https://github.com/0x192/universal-android-debloater/issues/78)] Packages not being actually uninstalled on older devices (< Android 6.0). Without root we can only use `pm block`/`pm unblock` for Android KitKit (4.4) and `pm hide`/`pm unhide` on Android Lollipop (5.x).
### Packaging
- For Arch-based users, UAD is now available in the AUR: `universal-android-debloater-bin` (binary) and `universal-android-debloater` (from source)
- Bump dependencies
## [0.3] - 2021-10-10
### Added
- Multi-user support (#16): You can now debloat/restore apps for any user of the phone (not only the primary user 0).
- [[#16](https://github.com/0x192/universal-android-debloater/issues/16)] Multi-user support: You can now debloat/restore apps for any user of the phone (not only the primary user 0).
- `Multi user mode` setting (default to `on` for Android 5+) allowing to remove packages for all users ([a work profile is another user](https://developer.android.com/work/managed-profiles)) instead of only the selected user.
- User switcher (picklist).
- New themes! (#11) : light, dark and lupin. Lupin theme is now the new default theme. Themes can be changed from the settings.
- Description field scrollbar: you can now scroll long descriptions (#40).
- [[#11](https://github.com/0x192/universal-android-debloater/issues/11)] New themes: light, dark and lupin. Lupin theme is now the new default theme. Themes can be changed from the settings.
- [[#40](https://github.com/0x192/universal-android-debloater/issues/40)] Description field scrollbar: you can now scroll long descriptions.
## Fixed
### Fixed
- [Regression] Unsafe packages can be deleted without enabling `expert mode`.
- The refresh button doesn't update settings when a (new) phone is connected.
- [Regression] Restore buttons are disabled when connecting an Android 8.0 phone.
- Refresh icon does not appear (#17)
- [[#17](https://github.com/0x192/universal-android-debloater/issues/17)] Refresh icon does not appear.
## [0.2.2] - 2021-09-30
## Fixed
### Fixed
- Crash when connecting a LG device (#33)
## [0.2.1] - 2021-09-28
## Added
### Added
- Software version in the navigation panel
## Packaging
### Packaging
- `wgpu` renderer is not the default renderer (you don't need to add `--features wgpu` if you want to build UAD with `wgpu`)
### Fixed
- Exported selection not found (#35)
- [[#35](https://github.com/0x192/universal-android-debloater/issues/35)] Exported selection not found
## [0.2] - 2021-09-26
### Added
- UAD now comes with a logger. Debug information will be written to a `uad.log` file (Warning level log in *stdout*) (#2)
- Support for older phone (< Android 8.0) (#15):
- [[#2](https://github.com/0x192/universal-android-debloater/issues/2)] UAD now comes with a logger. Debug information will be written to a `uad.log` file (Warning level log in *stdout*)
- [[#15](https://github.com/0x192/universal-android-debloater/issues/15)] Support for older phone (< Android 8.0):
- Disable mode in settings: clear and disable packages instead of uninstalling them (default for old phones because you can't restore uninstalled packages)
- Export your selection in the `uad_exported_selection.txt` file. Packages from this file (if found in the current directory) will be automatically selected upon the start of UAD (or after a refresh) (#8)
- [[#8](https://github.com/0x192/universal-android-debloater/issues/8)] Export your selection in the `uad_exported_selection.txt` file. Packages from this file (if found in the current directory) will be automatically selected upon the start of UAD (or after a refresh).
### Changed
- UAD will no longer crash at start if it doesn't find ADB but will display a useful error message (#25)
- Better handling of ADB errors (#3)
- [[#25](https://github.com/0x192/universal-android-debloater/issues/25)] UAD will no longer crash at start if it doesn't find ADB but will display a useful error message
- [[#3](https://github.com/0x192/universal-android-debloater/issues/3)] Better handling of ADB errors
- Updated dependencies (compatibility with [Iced](https://github.com/iced-rs/iced) main branch latest commit)
- Cleanup and refactoring of the code
- Performance improvement
- Various UI/UX improvement
- The `Debloat/Restore selection` button has been split in 2 buttons: `removing` and `restoring`
## Packaging
### Packaging
- Added an alternative build that uses [OpenGL](https://fr.wikipedia.org/wiki/OpenGL) (instead of [Vulkan](https://fr.wikipedia.org/wiki/Vulkan_(API))) for compatibility with older computers. If you encouter some visual glitches with the default Vulkan build you should try the OpenGL build.
### Fixed

2304
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,43 @@
cargo-features = ["strip"] # nightly
[package]
name = "uad_gui"
description = "A cross-platform GUI debloater for android devices"
version = "0.3.0"
version = "0.5.1"
authors = ["w1nst0n"]
license = "GPL-3.0"
homepage = "https://gitlab.com/W1nst0n/universal-android-debloater-rs"
repository = "https://gitlab.com/W1nst0n/universal-android-debloater-rs"
homepage = "https://github.com/0x192/universal-android-debloater"
repository = "https://github.com/0x192/universal-android-debloater"
readme = "README.md"
keywords = ["debloater", "android", "adb", "privacy", "bloatware"]
categories = ["gui"]
edition = "2021"
[features]
default = ["wgpu"]
wgpu = [] # iced/wgpu is default
glow = ["iced/glow"] # OpenGL support
default = ["wgpu", "self-update"]
wgpu = [] # Iced/wgpu is default
glow = ["iced/glow"] # OpenGL support
self-update = ["flate2", "tar"]
no-self-update = []
[dependencies]
iced = { git = "https://github.com/hecrj/iced.git" }
iced = { git = "https://github.com/iced-rs/iced.git" }
iced_native = { git = "https://github.com/iced-rs/iced.git" }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
static_init = "^1.0"
fern = { version = "^0.6", features = ["colored"] }
fern = { version = "^0", features = ["colored"] }
chrono = { version = "^0.4", default-features = false, features = ["std", "clock"] }
log = "^0.4"
chrono = "^0.4"
regex = "^1.5"
toml = "^0"
dirs = "^5.0.0"
ureq = { version = "*", features = ["json"] }
retry = { version = "^2.0.0" }
[target.'cfg(not(target_os = "windows"))'.dependencies]
flate2 = { version = "^1", optional = true }
tar = { version = "^0.4", optional = true }
[profile.release]
opt-level = "s"
lto = true
strip = "symbols"
strip = "symbols"

View file

@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Universal Android Debloater GUI
Copyright (C) 2021 W1nst0n
Copyright (C) 2021 W1nst0n
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) 2021 W1nst0n
Universal Android Debloater GUI Copyright (C) 2021 W1nst0n
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

236
README.md
View file

@ -1,133 +1,179 @@
# Universal Android Debloater GUI
**DISCLAIMER**: Use it at your own risk. I am not responsible for anything that could happen to your phone.
![uad-rust-gui-screenshot](ressources/screenshots/v0.3.png)
**DISCLAIMER**: Use at your own risk. I am not responsible for anything that
could happen to your phone.
**This software is still in a early stage of developpement. Check out the issues, and feel free to contribute!**
<img src="/resources/screenshots/v0.5.0.png" width="850" alt="uad_screenshot">
**This software is still in an early stage of development. Check out the issues, and feel free to contribute!**
## Summary
This is a complete rewrite in Rust of the [UAD project](https://gitlab.com/W1nst0n/universal-android-debloater) which aims at improving privacy and battery performance by removing unnecessary and obscure system apps. This can also contribute to improve security by reducing [the attack surface](https://en.wikipedia.org/wiki/Attack_surface).
Packages are as well documented as possible in order to provide a better understanding of what you can delete or not. The worse thing which could happen is removing an essential system package needed during boot causing then an unfortunate bootloop. After about 5 failed system boots, the phone will automatically reboot in recovery mode and you'll have to perform a FACTORY RESET. So make a backup!
This is a complete rewrite in Rust of the [UAD project](https://gitlab.com/W1nst0n/universal-android-debloater),
which aims to improve privacy and battery performance by removing unnecessary
and obscure system apps.
This can also contribute to improve security by reducing [the attack surface](https://en.wikipedia.org/wiki/Attack_surface).
In any case, you can NOT brick your device with this software! That's the main thing, right?
Packages are as well documented as possible in order to provide a better
understanding of what you can delete or not. The worst issue that could happen
is removing an essential system package needed during boot causing then an unfortunate
bootloop. After about 5 failed system boots, the phone will automatically reboot
in recovery mode, and you'll have to perform a FACTORY RESET. Make a backup first!
## Features
* [X] Uninstall/Disable and Restore/Enable system packages
* [X] Multi-user support (e.g apps in work profiles)
* [X] Export/Import your selection in `uad_exported_selection.txt`
* [X] Logging of all your action in the `uad.log` file
In any case, you **CANNOT** brick your device with this software!
That's the main point, right?
NB : System apps cannot really be uninstalled without root (see the [FAQ](https://gitlab.com/W1nst0n/universal-android-debloater-rs/-/wikis/FAQ))
## Features
## Universal debloat lists
* [X] GFAM (Google/Facebook/Amazon/Microsoft)
* [X] AOSP
* [X] Manufacturers (OEM)
* [X] Mobile carriers
* [X] Qualcomm / Mediatek / Miscellaneous
- [x] Uninstall/Disable and Restore/Enable system packages
- [x] Multi-user support (e.g. apps in work profiles)
- [x] Export/Import your selection in `uad_exported_selection.txt`
- [x] Multi-device support: you can connect multiple phones at the same time
- [x] All your actions are logged, so you never forget what you've done
NB : System apps cannot truly be uninstalled without root (see the [FAQ](https://github.com/0x192/universal-android-debloater/wiki/FAQ))
## Universal Debloat Lists
- [x] GFAM (Google/Facebook/Amazon/Microsoft)
- [x] AOSP
- [x] Manufacturers (OEM)
- [x] Mobile carriers
- [x] Qualcomm / Mediatek / Miscellaneous
## Manufacturers debloat lists
* [ ] Archos
* [X] Asus
* [ ] Blackberry
* [ ] Gionee
* [X] LG
* [X] Google
* [ ] HTC
* [X] Huawei
* [X] Motorola
* [X] Nokia
* [X] OnePlus
* [X] Oppo
* [X] Samsung
* [X] Sony
* [ ] TCL
* [ ] Wiko
* [X] Xiaomi
* [ ] ZTE
- [ ] Archos
- [x] Asus
- [ ] Blackberry
- [ ] Gionee
- [x] LG
- [x] Google
- [ ] iQOO
- [x] Fairphone
- [ ] HTC
- [x] Huawei
- [x] Motorola
- [x] Nokia
- [x] OnePlus
- [x] Oppo
- [x] Realme
- [x] Samsung
- [x] Sony
- [x] Tecno
- [ ] TCL
- [x] Unihertz
- [x] Vivo/iQOO
- [ ] Wiko
- [x] Xiaomi
- [x] ZTE
## Mobile carriers debloat lists
| Country | Carriers |
|-----------------|-----------------------------------|
| France | Orange, SFR, Free, Bouyges/Sosh |
| USA | T-Mobile, Verizon, Sprint, AT&T |
| Germany | Telekom |
| Country | Carriers |
| ------- | ------------------------------- |
| France | Orange, SFR, Free, Bouygues |
| USA | T-Mobile, Verizon, Sprint, AT&T |
| Germany | Telekom |
| UK | EE |
## How to use it
## How to use it
- **Read the [FAQ](https://github.com/0x192/universal-android-debloater/wiki/FAQ)!**
- **Do a proper backup of your data! You can never be too careful!**
- Enable *Developer Options* on your smartphone.
- Turn on *USB Debugging* from the developper panel.
- From the settings, disconnect from any OEM accounts (when you delete OEM account package it could lock you on the lockscreen because the phone can't associate your identity anymore)
- Install ADB:
<p>
<details>
<summary>LINUX</summary>
- Enable _Developer Options_ on your smartphone.
- Turn on _USB Debugging_ from the developer panel.
- From the settings, disconnect from any OEM accounts (when you delete an OEM
account package it could lock you on the lockscreen because the phone can't
associate your identity anymore)
- Install ADB (see the intructions by clicking on your OS below):
<p>
<details>
<summary>LINUX</summary>
- Install *Android platform tools* on your PC :
- Install _Android platform tools_ on your PC :
Debian Base:
```bash
$ sudo apt install android-sdk-platform-tools
```
Arch-Linux Base:
```bash
$ sudo pacman -S android-tools
```
Red Hat Base:
```bash
$ sudo yum install android-tools
```
OpenSUSE Base:
```bash
$ sudo zypper install android-tools
```
</details>
</p>
Debian Base:
<p>
<details>
<summary>MAC OS</summary>
```bash
sudo apt install android-sdk-platform-tools
```
- Install [Homebrew](https://brew.sh/)
- Install *Android platform tools*
Arch-Linux Base:
```bash
$ brew install android-platform-tools
```
</details>
</p>
```bash
sudo pacman -S android-tools
```
<p>
<details>
<summary>WINDOWS</summary>
Red Hat Base:
- Download [android platform tools](https://dl.google.com/android/repository/platform-tools-latest-windows.zip) and unzip it somewhere. [Add the folder to your PATH](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/).
```bash
sudo yum install android-tools
```
- [Install USB drivers of your device](https://developer.android.com/studio/run/oem-usb#Drivers)
- Check your device is detected:
```batch
> adb devices
```
</details>
</p>
OpenSUSE Base:
```bash
sudo zypper install android-tools
```
- Download the latest stable release of UAD GUI [here](https://github.com/0x192/universal-android-debloater/releases).
</details>
</p>
**NOTE:** Chinese phones users may need to use the AOSP list for removing some stock apps because those chinese manufacturers (especially Xiaomi and Huawei) have been using the name of AOSP packages for their own (modified & closed-source) apps.
<p>
<details>
<summary>MAC OS</summary>
**IMPORTANT NOTE:** You will have to run this software whenever your OEM push an update to your phone as some *uninstalled* system apps could be reinstalled.
- Install [Homebrew](https://brew.sh/)
- Install _Android platform tools_
```bash
brew install android-platform-tools
```
</details>
</p>
<p>
<details>
<summary>WINDOWS</summary>
- Download [android platform tools](https://dl.google.com/android/repository/platform-tools-latest-windows.zip)
and unzip it somewhere.
- [Add the android platform tools to your PATH](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/)
**OR** make sure to launch UAD from the same directory.
- [Install USB drivers for your device](https://developer.android.com/studio/run/oem-usb#Drivers)
- Check your device is detected:
```bash
adb devices
```
</details>
</p>
- Download the latest release of UAD GUI for your Operating System [here](https://github.com/0x192/universal-android-debloater/releases).
Take the `opengl` version only if the default version (with a Vulkan backend)
doesn't launch.
**NOTE:** Chinese phones users may need to use the AOSP list for removing some stock
apps because those Chinese manufacturers (especially Xiaomi and Huawei) have been
using the name of AOSP packages for their own (modified & closed-source) apps.
**IMPORTANT NOTE:** You will have to run this software whenever your OEM pushes
an update to your phone as some _uninstalled_ system apps could be reinstalled.
## How to contribute
Hey-hey-hey! Don't go away so fast! This is a community project. That means I need you! I'm sure you want to make this project better anyway.
Hey-hey-hey! Don't go away so fast! This is a community project.
That means I need you! I'm sure you want to make this project better anyway.
==> [How to contribute](https://github.com/0x192/universal-android-debloater/wiki)
## Special thanks
- [@mawilms](https://github.com/mawilms) for his LotRO plugin manager ([Lembas](https://github.com/mawilms/lembas)) which helped me a lot to understand how to use the [Iced](https://github.com/hecrj/iced) GUI library.
- [@casperstorm](https://github.com/casperstorm) for the UI/UX inspiration.
- [@mawilms](https://github.com/mawilms) for his LotRO plugin manager ([Lembas](https://github.com/mawilms/lembas))
which helped me a lot to understand how to use the [Iced](https://github.com/hecrj/iced)
GUI library.
- [@casperstorm](https://github.com/casperstorm) for the UI/UX inspiration.

View file

@ -1,16 +0,0 @@
use std::env::var;
fn main() {
let target = var("TARGET").unwrap();
println!("cargo:rerun-if-changed=build.rs");
if target.contains("windows") && !target.contains("msvc") {
println!("cargo:rerun-if-env-changed=WINAPI_NO_BUNDLED_LIBRARIES");
println!("cargo:rerun-if-env-changed=WINAPI_STATIC_NOBUNDLE");
println!("cargo:rustc-link-lib=static=gcc");
println!("cargo:rustc-link-lib=static=stdc++");
println!("cargo:rustc-link-lib=static=winpthread");
}
}

10621
resources/assets/icons.json Normal file

File diff suppressed because one or more lines are too long

BIN
resources/assets/icons.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Binary file not shown.

86
src/core/config.rs Normal file
View file

@ -0,0 +1,86 @@
use crate::core::sync::{get_android_sdk, User};
use crate::core::utils::DisplayablePath;
use crate::gui::views::settings::Settings;
use crate::CONFIG_DIR;
use serde::{Deserialize, Serialize};
use static_init::dynamic;
use std::fs;
use std::path::PathBuf;
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub general: GeneralSettings,
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
pub devices: Vec<DeviceSettings>,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct GeneralSettings {
pub theme: String,
pub expert_mode: bool,
}
#[derive(Default, Debug, Clone)]
pub struct BackupSettings {
pub backups: Vec<DisplayablePath>,
pub selected: Option<DisplayablePath>,
pub users: Vec<User>,
pub selected_user: Option<User>,
pub backup_state: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DeviceSettings {
pub device_id: String,
pub disable_mode: bool,
pub multi_user_mode: bool,
#[serde(skip)]
pub backup: BackupSettings,
}
impl Default for DeviceSettings {
fn default() -> Self {
Self {
device_id: String::new(),
multi_user_mode: get_android_sdk() > 21,
disable_mode: false,
backup: BackupSettings::default(),
}
}
}
#[dynamic]
static CONFIG_FILE: PathBuf = CONFIG_DIR.join("config.toml");
impl Config {
pub fn save_changes(settings: &Settings, device_id: &String) {
let mut config = Self::load_configuration_file();
if let Some(device) = config
.devices
.iter_mut()
.find(|x| x.device_id == *device_id)
{
*device = settings.device.clone();
} else {
debug!("config: New device settings saved");
config.devices.push(settings.device.clone());
}
config.general = settings.general.clone();
let toml = toml::to_string(&config).unwrap();
fs::write(&*CONFIG_FILE, toml).expect("Could not write config file to disk!");
}
pub fn load_configuration_file() -> Self {
match fs::read_to_string(&*CONFIG_FILE) {
Ok(s) => match toml::from_str(&s) {
Ok(config) => return config,
Err(e) => error!("Invalid config file: `{}`", e),
},
Err(e) => error!("Failed to read config file: `{}`", e),
}
error!("Restoring default config file");
let toml = toml::to_string(&Self::default()).unwrap();
fs::write(&*CONFIG_FILE, toml).expect("Could not write config file to disk!");
Self::default()
}
}

View file

@ -1,4 +1,7 @@
pub mod config;
pub mod save;
pub mod sync;
pub mod theme;
pub mod uad_lists;
pub mod update;
pub mod utils;

179
src/core/save.rs Normal file
View file

@ -0,0 +1,179 @@
use crate::core::config::DeviceSettings;
use crate::core::sync::{apply_pkg_state_commands, CorePackage, Phone, User};
use crate::core::utils::DisplayablePath;
use crate::gui::widgets::package_row::PackageRow;
use crate::CACHE_DIR;
use serde::{Deserialize, Serialize};
use static_init::dynamic;
use std::fs;
use std::path::{Path, PathBuf};
#[dynamic]
pub static BACKUP_DIR: PathBuf = CACHE_DIR.join("backups");
#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
struct PhoneBackup {
device_id: String,
users: Vec<UserBackup>,
}
#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
struct UserBackup {
id: u16,
packages: Vec<CorePackage>,
}
// Backup all `Uninstalled` and `Disabled` packages
pub async fn backup_phone(
users: Vec<User>,
device_id: String,
phone_packages: Vec<Vec<PackageRow>>,
) -> Result<(), String> {
let mut backup = PhoneBackup {
device_id: device_id.clone(),
..PhoneBackup::default()
};
for u in users {
let mut user_backup = UserBackup {
id: u.id,
..UserBackup::default()
};
for p in phone_packages[u.index].clone() {
user_backup.packages.push(CorePackage {
name: p.name.clone(),
state: p.state,
});
}
backup.users.push(user_backup);
}
match serde_json::to_string_pretty(&backup) {
Ok(json) => {
let backup_path = &*BACKUP_DIR.join(device_id);
if let Err(e) = fs::create_dir_all(backup_path) {
error!("BACKUP: could not create backup dir: {}", e);
return Err(e.to_string());
};
let backup_filename =
format!("{}.json", chrono::Local::now().format("%Y-%m-%d_%H-%M-%S"));
match fs::write(backup_path.join(backup_filename), json) {
Ok(_) => Ok(()),
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
pub fn list_available_backups(dir: &Path) -> Vec<DisplayablePath> {
#[allow(clippy::option_if_let_else)]
match fs::read_dir(dir) {
Ok(files) => files
.filter_map(|e| e.ok())
.map(|e| DisplayablePath { path: e.path() })
.collect::<Vec<_>>(),
Err(_) => vec![],
}
}
pub fn list_available_backup_user(backup: DisplayablePath) -> Vec<User> {
match fs::read_to_string(backup.path) {
Ok(data) => {
let phone_backup: PhoneBackup =
serde_json::from_str(&data).expect("Unable to parse backup file");
let mut users = vec![];
for u in phone_backup.users {
users.push(User {
id: u.id,
index: 0,
protected: false,
});
}
users
}
Err(e) => {
error!("[BACKUP]: Selected backup file not found: {}", e);
vec![]
}
}
}
#[derive(Debug)]
pub struct BackupPackage {
pub index: usize,
pub commands: Vec<String>,
}
pub fn restore_backup(
selected_device: &Phone,
packages: &[Vec<PackageRow>],
settings: &DeviceSettings,
) -> Result<Vec<BackupPackage>, String> {
match fs::read_to_string(
settings
.backup
.selected
.as_ref()
.ok_or("field should be Some type")?
.path
.clone(),
) {
Ok(data) => {
let phone_backup: PhoneBackup =
serde_json::from_str(&data).expect("Unable to parse backup file");
let mut commands = vec![];
for u in phone_backup.users {
let index = match selected_device.user_list.iter().find(|x| x.id == u.id) {
Some(i) => i.index,
None => return Err(format!("user {} doesn't exist", u.id)),
};
for (i, backup_package) in u.packages.iter().enumerate() {
let package: CorePackage;
match packages[index]
.iter()
.find(|x| x.name == backup_package.name)
{
Some(p) => package = p.into(),
None => {
return Err(format!(
"{} not found for user {}",
backup_package.name, u.id
))
}
}
let p_commands = apply_pkg_state_commands(
&package,
backup_package.state,
&settings
.backup
.selected_user
.ok_or("field should be Some type")?,
selected_device,
);
if !p_commands.is_empty() {
commands.push(BackupPackage {
index: i,
commands: p_commands,
});
}
}
}
if !commands.is_empty() {
commands.push(BackupPackage {
index: 0,
commands: vec![],
});
}
Ok(commands)
}
Err(e) => Err(e.to_string()),
}
}

View file

@ -1,99 +1,163 @@
use crate::core::uad_lists::PackageState;
use crate::gui::views::settings::Settings;
use crate::gui::views::list::PackageInfo;
use crate::gui::widgets::package_row::PackageRow;
use regex::Regex;
use retry::{delay::Fixed, retry, OperationResult};
use serde::{Deserialize, Serialize};
use static_init::dynamic;
use std::collections::HashSet;
use std::env;
use std::process::Command;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
#[derive(Debug)]
#[dynamic]
static RE: Regex = Regex::new(r"\n(\S+)\s+device").unwrap();
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Phone {
pub model: String,
pub android_sdk: u8,
pub user_list: Vec<User>,
pub adb_id: String,
}
impl Default for Phone {
fn default() -> Self {
Self {
model: get_phone_brand(),
android_sdk: get_android_sdk(),
user_list: get_user_list(),
model: "fetching devices...".to_string(),
android_sdk: 0,
user_list: vec![],
adb_id: String::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
impl std::fmt::Display for Phone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.model)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Copy)]
pub struct User {
pub id: u16,
pub index: usize,
pub protected: bool,
}
impl std::fmt::Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", format!("user {}", self.id),)
write!(f, "user {}", self.id)
}
}
pub fn adb_shell_command(args: &str) -> Result<String, String> {
pub fn adb_shell_command(shell: bool, args: &str) -> Result<String, String> {
let adb_command = if shell {
vec!["shell", args]
} else {
vec![args]
};
let mut command = Command::new("adb");
command.args(adb_command);
#[cfg(target_os = "windows")]
let output = Command::new("adb")
.args(&["shell", args])
.creation_flags(0x08000000) // do not open a cmd window
.output();
let command = command.creation_flags(0x08000000); // do not open a cmd window
#[cfg(target_os = "macos")]
let output = Command::new("adb").args(&["shell", args]).output();
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let output = Command::new("adb").args(&["shell", args]).output();
match output {
match command.output() {
Err(e) => {
error!("ADB: {}", e);
Err("ADB was not found".to_string())
}
Ok(o) => {
if !o.status.success() {
let stdout = String::from_utf8(o.stdout).unwrap().trim_end().to_string();
let stderr = String::from_utf8(o.stderr).unwrap().trim_end().to_string();
if o.status.success() {
Ok(String::from_utf8(o.stdout)
.map_err(|e| e.to_string())?
.trim_end()
.to_string())
} else {
let stdout = String::from_utf8(o.stdout)
.map_err(|e| e.to_string())?
.trim_end()
.to_string();
let stderr = String::from_utf8(o.stderr)
.map_err(|e| e.to_string())?
.trim_end()
.to_string();
// ADB does really weird things. Some errors are not redirected to stderr
let err = if stdout.is_empty() { stderr } else { stdout };
Err(err)
} else {
Ok(String::from_utf8(o.stdout).unwrap().trim_end().to_string())
}
}
}
}
pub fn list_all_system_packages(user_id: &Option<&User>) -> String {
let action = match user_id {
Some(user_id) => format!("pm list packages -s -u --user {}", user_id.id),
None => "pm list packages -s -u".to_string(),
#[derive(Debug, Clone)]
pub enum CommandType {
PackageManager(PackageInfo),
Shell,
}
pub async fn perform_adb_commands(
action: String,
command_type: CommandType,
) -> Result<CommandType, ()> {
let label = match command_type {
CommandType::PackageManager(ref p) => p.removal.to_string(),
CommandType::Shell => "Shell".to_string(),
};
adb_shell_command(&action)
.unwrap_or_else(|_| "".to_string())
match adb_shell_command(true, &action) {
Ok(o) => {
// On old devices, adb commands can return the '0' exit code even if there
// is an error. On Android 4.4, ADB doesn't check if the package exists.
// It does not return any error if you try to `pm block` a non-existent package.
// Some commands are even killed by ADB before finishing and UAD can't catch
// the output.
if ["Error", "Failure"].iter().any(|&e| o.contains(e)) {
error!("[{}] {} -> {}", label, action, o);
Err(())
} else {
info!("[{}] {} -> {}", label, action, o);
Ok(command_type)
}
}
Err(err) => {
if !err.contains("[not installed for") {
error!("[{}] {} -> {}", label, action, err);
}
Err(())
}
}
}
#[allow(clippy::option_if_let_else)]
pub fn user_flag(user_id: Option<&User>) -> String {
match user_id {
Some(user_id) => format!(" --user {}", user_id.id),
None => "".to_string(),
}
}
pub fn list_all_system_packages(user_id: Option<&User>) -> String {
let action = format!("pm list packages -s -u{}", user_flag(user_id));
adb_shell_command(true, &action)
.unwrap_or_else(|_| String::new())
.replace("package:", "")
}
pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) -> HashSet<String> {
let user = match user_id {
Some(user_id) => format!(" --user {}", user_id.id),
None => "".to_string(),
};
pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> HashSet<String> {
let user = user_flag(user_id);
let action = match state {
PackageState::Enabled => format!("pm list packages -s -e{}", user),
PackageState::Disabled => format!("pm list package -s -d{}", user),
_ => "".to_string(), // You probably don't need to use this function for anything else
PackageState::Enabled => format!("pm list packages -s -e{user}"),
PackageState::Disabled => format!("pm list package -s -d{user}"),
_ => String::new(), // You probably don't need to use this function for anything else
};
adb_shell_command(&action)
adb_shell_command(true, &action)
.unwrap_or_default()
.replace("package:", "")
.lines()
@ -101,162 +165,172 @@ pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) ->
.collect()
}
pub fn action_handler(
selected_user: &User,
package: &PackageRow,
phone: &Phone,
settings: &Settings,
) -> Result<bool, bool> {
let actions: Vec<String> = match package.state {
PackageState::Enabled => match settings.disable_mode {
true => {
if phone.android_sdk < 21 {
vec![
format!("am force-stop {}", package.name),
format!("pm disable-user {}", package.name),
format!("pm clear {}", package.name),
]
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.flat_map(|u| {
[
format!("am force-stop --user {} {}", u.id, package.name),
format!("pm disable-user --user {} {}", u.id, package.name),
format!("pm clear --user {} {}", u.id, package.name),
]
})
.collect()
} else {
vec![
format!("am force-stop --user {} {}", selected_user.id, package.name),
format!(
"pm disable-user --user {} {}",
selected_user.id, package.name
),
format!("pm clear --user {} {}", selected_user.id, package.name),
]
}
}
false => {
if phone.android_sdk < 21 {
vec![format!("pm uninstall {}", package.name)]
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.map(|u| format!("pm uninstall --user {} {}", u.id, package.name))
.collect()
} else {
vec![format!(
"pm uninstall --user {} {}",
selected_user.id, package.name
)]
}
}
},
PackageState::Uninstalled => {
if phone.android_sdk < 21 {
Vec::new() // action already prevented by the GUI
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.map(|u| {
format!(
"cmd package install-existing --user {} {}",
u.id, package.name
)
})
.collect()
} else {
vec![format!(
"cmd package install-existing --user {} {}",
selected_user.id, package.name
)]
}
}
PackageState::Disabled => {
if phone.android_sdk < 21 {
vec![format!("pm enable {}", package.name)]
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.map(|u| format!("pm enable --user {} {}", u.id, package.name))
.collect()
} else {
vec![format!(
"pm enable --user {} {}",
selected_user.id, package.name
)]
}
}
PackageState::All => vec![], // This can't happen (like... never)
};
// Minimum information for processing adb commands
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct CorePackage {
pub name: String,
pub state: PackageState,
}
for action in actions {
match adb_shell_command(&action) {
Ok(_) => {
info!("[{}] {}", package.removal, action);
}
Err(err) => {
if err.contains("[not installed for") {
} else {
error!("[{}] {} -> {}", package.removal, action, err);
return Err(false);
}
}
impl From<&mut PackageRow> for CorePackage {
fn from(pr: &mut PackageRow) -> Self {
Self {
name: pr.name.clone(),
state: pr.state,
}
}
Ok(true)
}
impl From<PackageRow> for CorePackage {
fn from(pr: PackageRow) -> Self {
Self {
name: pr.name.clone(),
state: pr.state,
}
}
}
impl From<&PackageRow> for CorePackage {
fn from(pr: &PackageRow) -> Self {
Self {
name: pr.name.clone(),
state: pr.state,
}
}
}
pub fn apply_pkg_state_commands(
package: &CorePackage,
wanted_state: PackageState,
selected_user: &User,
phone: &Phone,
) -> Vec<String> {
// https://github.com/0x192/universal-android-debloater/wiki/ADB-reference
// ALWAYS PUT THE COMMAND THAT CHANGES THE PACKAGE STATE FIRST!
let commands = match wanted_state {
PackageState::Enabled => {
match package.state {
PackageState::Disabled => match phone.android_sdk {
i if i >= 23 => vec!["pm enable"],
_ => vec!["pm enable"],
},
PackageState::Uninstalled => match phone.android_sdk {
i if i >= 23 => vec!["cmd package install-existing"],
21 | 22 => vec!["pm unhide"],
19 | 20 => vec!["pm unblock", "pm clear"],
_ => vec![], // Impossible action already prevented by the GUI
},
_ => vec![],
}
}
PackageState::Disabled => match package.state {
PackageState::Uninstalled | PackageState::Enabled => match phone.android_sdk {
sdk if sdk >= 23 => vec!["pm disable-user", "am force-stop", "pm clear"],
_ => vec![],
},
_ => vec![],
},
PackageState::Uninstalled => match package.state {
PackageState::Enabled | PackageState::Disabled => match phone.android_sdk {
sdk if sdk >= 23 => vec!["pm uninstall"], // > Android Marshmallow (6.0)
21 | 22 => vec!["pm hide", "pm clear"], // Android Lollipop (5.x)
19 | 20 => vec!["pm block", "pm clear"], // Android KitKat (4.4/4.4W)
_ => vec!["pm uninstall"], // Disable mode is unavailable on older devices because the specific ADB commands need root
},
_ => vec![],
},
PackageState::All => vec![],
};
if phone.android_sdk < 21 {
request_builder(&commands, &package.name, None)
} else {
request_builder(&commands, &package.name, Some(selected_user))
}
}
pub fn request_builder(commands: &[&str], package: &str, user: Option<&User>) -> Vec<String> {
#[allow(clippy::option_if_let_else)]
match user {
Some(u) => commands
.iter()
.map(|c| format!("{} --user {} {}", c, u.id, package))
.collect(),
None => commands.iter().map(|c| format!("{c} {package}")).collect(),
}
}
pub fn get_phone_model() -> String {
match adb_shell_command("getprop ro.product.model") {
Ok(model) => model,
Err(err) => {
println!("ERROR: {}", err);
if err.contains("adb: no devices/emulators found") {
"no devices/emulators found".to_string()
} else {
err
}
adb_shell_command(true, "getprop ro.product.model").unwrap_or_else(|err| {
println!("ERROR: {err}");
if err.contains("adb: no devices/emulators found") {
"no devices/emulators found".to_string()
} else {
err
}
}
})
}
pub fn get_android_sdk() -> u8 {
match adb_shell_command("getprop ro.build.version.sdk") {
Ok(sdk) => sdk.parse().unwrap(),
Err(_) => 0,
}
adb_shell_command(true, "getprop ro.build.version.sdk").map_or(0, |sdk| sdk.parse().unwrap())
}
pub fn get_phone_brand() -> String {
format!(
"{} {}",
adb_shell_command("getprop ro.product.brand")
.unwrap_or_else(|_| "".to_string())
.trim(),
adb_shell_command(true, "getprop ro.product.brand")
.map(|s| s.trim().to_string())
.unwrap_or_default(),
get_phone_model()
)
}
pub fn is_protected_user(user_id: &str) -> bool {
adb_shell_command(true, &format!("pm list packages --user {user_id}")).is_err()
}
pub fn get_user_list() -> Vec<User> {
#[dynamic]
static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap();
match adb_shell_command("pm list users") {
Ok(users) => RE
.find_iter(&users)
.enumerate()
.map(|(i, u)| User {
id: u.as_str()[1..].parse().unwrap(),
index: i,
})
.collect(),
Err(_) => vec![],
}
adb_shell_command(true, "pm list users")
.map(|users| {
RE.find_iter(&users)
.enumerate()
.map(|(i, u)| User {
id: u.as_str()[1..].parse().unwrap(),
index: i,
protected: is_protected_user(&u.as_str()[1..]),
})
.collect()
})
.unwrap_or_default()
}
// getprop ro.serialno
pub async fn get_devices_list() -> Vec<Phone> {
retry(
Fixed::from_millis(500).take(120),
|| match adb_shell_command(false, "devices") {
Ok(devices) => {
let mut device_list: Vec<Phone> = vec![];
if !RE.is_match(&devices) {
return OperationResult::Retry(vec![]);
}
for device in RE.captures_iter(&devices) {
env::set_var("ANDROID_SERIAL", &device[1]);
device_list.push(Phone {
model: get_phone_brand(),
android_sdk: get_android_sdk(),
user_list: get_user_list(),
adb_id: device[1].to_string(),
});
}
OperationResult::Ok(device_list)
}
Err(err) => {
error!("get_device_list() -> {}", err);
let test: Vec<Phone> = vec![];
OperationResult::Retry(test)
}
},
)
.unwrap_or_default()
}

View file

@ -1,10 +1,11 @@
use iced::Color;
use std::cmp::Ordering;
use iced::{color, Color};
#[derive(Debug, Clone)]
pub struct Theme {
pub name: String,
pub palette: ColorPalette,
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub enum Theme {
#[default]
Lupin,
Dark,
Light,
}
#[derive(Debug, Clone, Copy)]
@ -37,101 +38,65 @@ pub struct ColorPalette {
}
impl Theme {
pub fn all() -> Vec<Theme> {
vec![Theme::dark(), Theme::light(), Theme::lupin()]
}
pub fn dark() -> Theme {
Theme {
name: "Dark".to_string(),
palette: ColorPalette {
pub const ALL: [Self; 3] = [Self::Lupin, Self::Dark, Self::Light];
pub fn palette(self) -> ColorPalette {
match self {
Self::Dark => ColorPalette {
base: BaseColors {
background: hex_to_color("#111111").unwrap(),
foreground: hex_to_color("#1C1C1C").unwrap(),
background: color!(0x111111),
foreground: color!(0x1C1C1C),
},
normal: NormalColors {
primary: hex_to_color("#3f2b56").unwrap(),
secondary: hex_to_color("#386e50").unwrap(),
surface: hex_to_color("#828282").unwrap(),
error: hex_to_color("#992B2B").unwrap(),
primary: color!(0x5E4266),
secondary: color!(0x386e50),
surface: color!(0x828282),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: hex_to_color("#BA84FC").unwrap(),
secondary: hex_to_color("#49eb7a").unwrap(),
surface: hex_to_color("#E0E0E0").unwrap(),
error: hex_to_color("#C13047").unwrap(),
primary: color!(0xBA84FC),
secondary: color!(0x49eb7a),
surface: color!(0xE0E0E0),
error: color!(0xC13047),
},
},
Self::Light => ColorPalette {
base: BaseColors {
background: color!(0xEEEEEE),
foreground: color!(0xE0E0E0),
},
normal: NormalColors {
primary: color!(0x230F08),
secondary: color!(0xF9D659),
surface: color!(0x818181),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: color!(0x673AB7),
secondary: color!(0x3797A4),
surface: color!(0x000000),
error: color!(0xC13047),
},
},
Self::Lupin => ColorPalette {
base: BaseColors {
background: color!(0x282a36),
foreground: color!(0x353746),
},
normal: NormalColors {
primary: color!(0x58406F),
secondary: color!(0x386e50),
surface: color!(0xa2a4a3),
error: color!(0xA13034),
},
bright: BrightColors {
primary: color!(0xbd94f9),
secondary: color!(0x49eb7a),
surface: color!(0xf4f8f3),
error: color!(0xE63E6D),
},
},
}
}
pub fn light() -> Theme {
Theme {
name: "Light".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#EEEEEE").unwrap(),
foreground: hex_to_color("#E0E0E0").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#673AB7").unwrap(),
secondary: hex_to_color("#F9D659").unwrap(),
surface: hex_to_color("#818181").unwrap(),
error: hex_to_color("#992B2B").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#673AB7").unwrap(),
secondary: hex_to_color("#3797A4").unwrap(),
surface: hex_to_color("#000000").unwrap(),
error: hex_to_color("#C13047").unwrap(),
},
},
}
}
pub fn lupin() -> Theme {
Theme {
name: "Lupin".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#282a36").unwrap(),
foreground: hex_to_color("#353746").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#483e61").unwrap(),
secondary: hex_to_color("#386e50").unwrap(),
surface: hex_to_color("#a2a4a3").unwrap(),
error: hex_to_color("#A13034").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#bd94f9").unwrap(),
secondary: hex_to_color("#49eb7a").unwrap(),
surface: hex_to_color("#f4f8f3").unwrap(),
error: hex_to_color("#E63E6D").unwrap(),
},
},
}
}
}
impl PartialEq for Theme {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl PartialOrd for Theme {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.name.cmp(&other.name))
}
}
impl Eq for Theme {}
impl Ord for Theme {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl std::fmt::Display for Theme {
@ -139,33 +104,11 @@ impl std::fmt::Display for Theme {
write!(
f,
"{}",
match self.name.as_str() {
"Dark" => "Dark",
"Light" => "Light",
"Lupin" => "Lupin",
_ => "Unknown theme",
match self {
Self::Dark => "Dark",
Self::Light => "Light",
Self::Lupin => "Lupin",
}
)
}
}
fn hex_to_color(hex: &str) -> Option<Color> {
if hex.len() == 7 {
let hash = &hex[0..1];
let r = u8::from_str_radix(&hex[1..3], 16);
let g = u8::from_str_radix(&hex[3..5], 16);
let b = u8::from_str_radix(&hex[5..7], 16);
return match (hash, r, g, b) {
("#", Ok(r), Ok(g), Ok(b)) => Some(Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}),
_ => None,
};
}
None
}

View file

@ -1,22 +1,27 @@
use serde::Deserialize;
use crate::core::utils::{format_diff_time_from_now, last_modified_date};
use crate::CACHE_DIR;
use retry::{delay::Fixed, retry, OperationResult};
use serde::{Deserialize, Serialize};
use serde_json;
//use std::fs;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Deserialize, Debug, Clone, PartialEq, Hash, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Package {
id: String,
pub list: UadList,
pub description: Option<String>,
dependencies: Option<String>,
needed_by: Option<String>,
labels: Option<Vec<String>>,
pub description: String,
dependencies: Vec<String>,
needed_by: Vec<String>,
labels: Vec<String>,
pub removal: Removal,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UadList {
#[default]
All,
Aosp,
Carrier,
@ -27,22 +32,36 @@ pub enum UadList {
Unlisted,
}
impl Default for UadList {
fn default() -> UadList {
UadList::All
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum UadListState {
#[default]
Downloading,
Done,
Failed,
}
impl std::fmt::Display for UadListState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let date = last_modified_date(CACHE_DIR.join("uad_lists.json"));
let s = match self {
Self::Downloading => "Checking updates...".to_string(),
Self::Done => format!("Done (last was {})", format_diff_time_from_now(date)),
Self::Failed => "Failed to check update!".to_string(),
};
write!(f, "{s}")
}
}
impl UadList {
pub const ALL: [UadList; 8] = [
UadList::All,
UadList::Aosp,
UadList::Carrier,
UadList::Google,
UadList::Misc,
UadList::Oem,
UadList::Pending,
UadList::Unlisted,
pub const ALL: [Self; 8] = [
Self::All,
Self::Aosp,
Self::Carrier,
Self::Google,
Self::Misc,
Self::Oem,
Self::Pending,
Self::Unlisted,
];
}
@ -52,40 +71,30 @@ impl std::fmt::Display for UadList {
f,
"{}",
match self {
UadList::All => "All lists",
UadList::Aosp => "aosp",
UadList::Carrier => "carrier",
UadList::Google => "google",
UadList::Misc => "misc",
UadList::Oem => "oem",
UadList::Pending => "pending",
UadList::Unlisted => "unlisted",
Self::All => "All lists",
Self::Aosp => "aosp",
Self::Carrier => "carrier",
Self::Google => "google",
Self::Misc => "misc",
Self::Oem => "oem",
Self::Pending => "pending",
Self::Unlisted => "unlisted",
}
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PackageState {
All,
#[default]
Enabled,
Uninstalled,
Disabled,
}
impl Default for PackageState {
fn default() -> PackageState {
PackageState::Enabled
}
}
impl PackageState {
pub const ALL: [PackageState; 4] = [
PackageState::All,
PackageState::Enabled,
PackageState::Uninstalled,
PackageState::Disabled,
];
pub const ALL: [Self; 4] = [Self::All, Self::Enabled, Self::Uninstalled, Self::Disabled];
}
impl std::fmt::Display for PackageState {
@ -94,10 +103,10 @@ impl std::fmt::Display for PackageState {
f,
"{}",
match self {
PackageState::All => "All packages",
PackageState::Enabled => "Enabled",
PackageState::Uninstalled => "Uninstalled",
PackageState::Disabled => "Disabled",
Self::All => "All packages",
Self::Enabled => "Enabled",
Self::Uninstalled => "Uninstalled",
Self::Disabled => "Disabled",
}
)
}
@ -108,26 +117,26 @@ pub trait Opposite {
}
impl Opposite for PackageState {
fn opposite(&self, disable: bool) -> PackageState {
fn opposite(&self, disable: bool) -> Self {
match self {
PackageState::Enabled => {
Self::Enabled => {
if disable {
PackageState::Disabled
Self::Disabled
} else {
PackageState::Uninstalled
Self::Uninstalled
}
}
PackageState::Uninstalled => PackageState::Enabled,
PackageState::Disabled => PackageState::Enabled,
PackageState::All => PackageState::All,
Self::Uninstalled | Self::Disabled => Self::Enabled,
Self::All => Self::All,
}
}
}
// Bad names. To be changed!
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default, Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Removal {
All,
#[default]
Recommended,
Advanced,
Expert,
@ -135,20 +144,14 @@ pub enum Removal {
Unlisted,
}
impl Default for Removal {
fn default() -> Removal {
Removal::Recommended
}
}
impl Removal {
pub const ALL: [Removal; 6] = [
Removal::All,
Removal::Recommended,
Removal::Advanced,
Removal::Expert,
Removal::Unsafe,
Removal::Unlisted,
pub const ALL: [Self; 6] = [
Self::All,
Self::Recommended,
Self::Advanced,
Self::Expert,
Self::Unsafe,
Self::Unlisted,
];
}
@ -158,29 +161,79 @@ impl std::fmt::Display for Removal {
f,
"{}",
match self {
Removal::All => "All",
Removal::Recommended => "Recommended",
Removal::Advanced => "Advanced",
Removal::Expert => "Expert",
Removal::Unsafe => "Unsafe",
Removal::Unlisted => "Unlisted",
Self::All => "All",
Self::Recommended => "Recommended",
Self::Advanced => "Advanced",
Self::Expert => "Expert",
Self::Unsafe => "Unsafe",
Self::Unlisted => "Unlisted",
}
)
}
}
pub fn load_debloat_lists() -> HashMap<String, Package> {
const DATA: &str = include_str!("../../ressources/assets/uad_lists.json");
let mut package_lists = HashMap::new();
//let data = fs::read_to_string("ressources/assets/uad_lists.json").expect("Unable to read file");
type PackageHashMap = HashMap<String, Package>;
pub fn load_debloat_lists(remote: bool) -> (Result<PackageHashMap, PackageHashMap>, bool) {
let cached_uad_lists: PathBuf = CACHE_DIR.join("uad_lists.json");
let mut error = false;
let list: Vec<Package> = if remote {
retry(Fixed::from_millis(1000).take(60), || {
match ureq::get(
"https://raw.githubusercontent.com/0x192/universal-android-debloater/\
main/resources/assets/uad_lists.json",
)
.call()
{
Ok(data) => {
let text = data.into_string().expect("response should be Ok type");
fs::write(cached_uad_lists.clone(), &text).expect("Unable to write file");
let list = serde_json::from_str(&text).expect("Unable to parse");
OperationResult::Ok(list)
}
Err(e) => {
warn!("Could not load remote debloat list: {}", e);
error = true;
OperationResult::Retry(Vec::<Package>::new())
}
}
})
.map_or_else(|_| get_local_lists(), |list| list)
} else {
warn!("Could not load remote debloat list");
get_local_lists()
};
// TODO: Do it without intermediary Vec?
let list: Vec<Package> = serde_json::from_str(DATA).expect("Unable to parse");
let mut package_lists = HashMap::new();
for p in list {
let name = p.id.clone();
package_lists.insert(name, p);
}
package_lists
if error {
(Err(package_lists), remote)
} else {
(Ok(package_lists), remote)
}
}
fn get_local_lists() -> Vec<Package> {
const DATA: &str = include_str!("../../resources/assets/uad_lists.json");
let cached_uad_lists = CACHE_DIR.join("uad_lists.json");
if Path::new(&cached_uad_lists).exists() {
let data = fs::read_to_string(cached_uad_lists).unwrap();
serde_json::from_str(&data).expect("Unable to parse")
} else {
serde_json::from_str(DATA).expect("Unable to parse")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_json() {
const DATA: &str = include_str!("../../resources/assets/uad_lists.json");
let _: Vec<Package> = serde_json::from_str(DATA).expect("Unable to parse");
}
}

299
src/core/update.rs Normal file
View file

@ -0,0 +1,299 @@
use serde::Deserialize;
#[cfg(feature = "self-update")]
use {
retry::{delay::Fibonacci, retry, OperationResult},
std::fs,
std::io,
std::io::copy,
std::path::Path,
std::path::PathBuf,
};
#[derive(Debug, Deserialize, Clone)]
pub struct Release {
pub tag_name: String,
pub assets: Vec<ReleaseAsset>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ReleaseAsset {
pub name: String,
#[serde(rename = "browser_download_url")]
pub download_url: String,
}
#[derive(Default, Debug, Clone)]
pub struct SelfUpdateState {
pub latest_release: Option<Release>,
pub status: SelfUpdateStatus,
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub enum SelfUpdateStatus {
Updating,
#[default]
Checking,
Done,
Failed,
}
impl std::fmt::Display for SelfUpdateStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Checking => "Checking updates...",
Self::Updating => "Updating...",
Self::Failed => "Failed to check update!",
Self::Done => "Done",
};
write!(f, "{s}")
}
}
/// Download a file from the internet
#[cfg(feature = "self-update")]
pub async fn download_file<T: ToString + Send>(url: T, dest_file: PathBuf) -> Result<(), String> {
let url = url.to_string();
debug!("downloading file from {}", &url);
match ureq::get(&url).call() {
Ok(res) => {
let mut file = fs::File::create(dest_file).map_err(|e| e.to_string())?;
if let Err(e) = copy(&mut res.into_reader(), &mut file) {
return Err(e.to_string());
}
}
Err(e) => return Err(e.to_string()),
}
Ok(())
}
/// Downloads the latest release file that matches `bin_name`, renames the current
/// executable to a temp path, renames the new version as the original file name,
/// then returns both the original file name (new version) and temp path (old version)
#[cfg(feature = "self-update")]
pub async fn download_update_to_temp_file(
bin_name: String,
release: Release,
) -> Result<(PathBuf, PathBuf), ()> {
let current_bin_path = std::env::current_exe().map_err(|_| ())?;
// Path to download the new version to
let download_path = current_bin_path
.parent()
.ok_or(())?
.join(format!("tmp_{bin_name}"));
// Path to temporarily force rename current process to, se we can then
// rename `download_path` to `current_bin_path` and then launch new version
// cleanly as `current_bin_path`
let tmp_path = current_bin_path
.parent()
.ok_or(())?
.join(format!("tmp2_{bin_name}"));
// MacOS and Linux release are gziped tarball
#[cfg(not(target_os = "windows"))]
{
let asset_name = format!("{bin_name}.tar.gz");
let asset = release
.assets
.iter()
.find(|a| a.name == asset_name)
.cloned()
.ok_or(())?;
let archive_path = current_bin_path.parent().ok_or(())?.join(&asset_name);
if let Err(e) = download_file(asset.download_url, archive_path.clone()).await {
error!("Couldn't download UAD update: {}", e);
return Err(());
}
if extract_binary_from_tar(&archive_path, &download_path).is_err() {
error!("Couldn't extract UAD release tarball");
return Err(());
}
std::fs::remove_file(&archive_path).map_err(|_| ())?;
}
// For Windows we download the new binary directly
#[cfg(target_os = "windows")]
{
let asset = release
.assets
.iter()
.find(|a| a.name == bin_name)
.cloned()
.ok_or(())?;
if let Err(e) = download_file(asset.download_url, download_path.clone()).await {
error!("Couldn't download UAD update: {}", e);
return Err(());
}
}
// Make the file executable
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(&download_path).map_err(|_| ())?.permissions();
permissions.set_mode(0o755);
if let Err(e) = fs::set_permissions(&download_path, permissions) {
error!("[SelfUpdate] Couldn't set permission to temp file: {}", e);
return Err(());
}
}
if let Err(e) = rename(&current_bin_path, &tmp_path) {
error!("[SelfUpdate] Couldn't rename binary path: {}", e);
return Err(());
}
if let Err(e) = rename(&download_path, &current_bin_path) {
error!("[SelfUpdate] Couldn't rename binary path: {}", e);
return Err(());
}
Ok((current_bin_path, tmp_path))
}
#[cfg(not(feature = "self-update"))]
pub fn get_latest_release() -> Result<Option<Release>, ()> {
Ok(None)
}
// UAD only has pre-releases so we can't use
// https://api.github.com/repos/0x192/universal-android-debloater/releases/latest
// to only get the latest release
#[cfg(feature = "self-update")]
pub fn get_latest_release() -> Result<Option<Release>, ()> {
debug!("Checking for UAD update");
match ureq::get("https://api.github.com/repos/0x192/universal-android-debloater/releases")
.call()
{
Ok(res) => {
let release: Release = serde_json::from_value(
res.into_json::<serde_json::Value>()
.map_err(|_| ())?
.get(0)
.ok_or(())?
.clone(),
)
.map_err(|_| ())?;
if release.tag_name.as_str() != "dev-build"
&& release.tag_name.as_str() > env!("CARGO_PKG_VERSION")
{
Ok(Some(release))
} else {
Ok(None)
}
}
Err(_) => {
debug!("Failed to check UAD update");
Err(())
}
}
}
/// Extracts the binary from a `tar.gz` archive to `temp_file` path
#[cfg(feature = "self-update")]
#[cfg(not(target_os = "windows"))]
pub fn extract_binary_from_tar(archive_path: &Path, temp_file: &Path) -> io::Result<()> {
use flate2::read::GzDecoder;
use std::fs::File;
use tar::Archive;
let mut archive = Archive::new(GzDecoder::new(File::open(archive_path)?));
let mut temp_file = File::create(temp_file)?;
for file in archive.entries()? {
let mut file = file?;
let path = file.path()?;
if path.to_str().is_some() {
io::copy(&mut file, &mut temp_file)?;
return Ok(());
}
}
Err(io::ErrorKind::NotFound.into())
}
/// Hardcoded binary names for each compilation target
/// that gets published to the Github Release
#[cfg(feature = "self-update")]
pub const fn bin_name() -> &'static str {
#[cfg(target_os = "windows")]
{
"uad_gui.exe"
}
#[cfg(target_os = "macos")]
{
"uad_gui-macos"
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
"uad_gui-linux"
}
}
/// Rename a file or directory to a new name, retrying if the operation fails because of permissions
///
/// Will retry for ~30 seconds with longer and longer delays between each, to allow for virus scan
/// and other automated operations to complete.
#[cfg(feature = "self-update")]
pub fn rename<F, T>(from: F, to: T) -> Result<(), String>
where
F: AsRef<Path>,
T: AsRef<Path>,
{
// 21 Fibonacci steps starting at 1 ms is ~28 seconds total
// See https://github.com/rust-lang/rustup/pull/1873 where this was used by Rustup to work around
// virus scanning file locks
let from = from.as_ref();
let to = to.as_ref();
retry(Fibonacci::from_millis(1).take(21), || {
match fs::rename(from, to) {
Ok(_) => OperationResult::Ok(()),
Err(e) => match e.kind() {
io::ErrorKind::PermissionDenied => OperationResult::Retry(e),
_ => OperationResult::Err(e),
},
}
})
.map_err(|e| e.to_string())
}
/// Remove a file, retrying if the operation fails because of permissions
///
/// Will retry for ~30 seconds with longer and longer delays between each, to allow for virus scan
/// and other automated operations to complete.
#[cfg(feature = "self-update")]
pub fn remove_file<P>(path: P) -> Result<(), String>
where
P: AsRef<Path>,
{
// 21 Fibonacci steps starting at 1 ms is ~28 seconds total
// See https://github.com/rust-lang/rustup/pull/1873 where this was used by Rustup to work around
// virus scanning file locks
let path = path.as_ref();
retry(
Fibonacci::from_millis(1).take(21),
|| match fs::remove_file(path) {
Ok(_) => OperationResult::Ok(()),
Err(e) => match e.kind() {
io::ErrorKind::PermissionDenied => OperationResult::Retry(e),
_ => OperationResult::Err(e),
},
},
)
.map_err(|e| e.to_string())
}

View file

@ -1,17 +1,17 @@
use crate::core::sync::{hashset_system_packages, list_all_system_packages, User};
use crate::core::theme::Theme;
use crate::core::uad_lists::{Package, PackageState, Removal, UadList};
use crate::gui::views::list::Selection;
use crate::gui::widgets::package_row::PackageRow;
use crate::gui::ICONS;
use iced::{alignment, Length, Text};
use chrono::offset::Utc;
use chrono::DateTime;
use std::collections::HashMap;
use std::fs;
use std::io::{self, prelude::*, BufReader};
use std::path::PathBuf;
use std::process::Command;
use std::{fmt, fs};
pub fn fetch_packages(
uad_lists: &'static HashMap<String, Package>,
user_id: &Option<&User>,
uad_lists: &HashMap<String, Package>,
user_id: Option<&User>,
) -> Vec<PackageRow> {
let all_system_packages = list_all_system_packages(user_id); // installed and uninstalled packages
let enabled_system_packages = hashset_system_packages(PackageState::Enabled, user_id);
@ -24,14 +24,14 @@ pub fn fetch_packages(
for p_name in all_system_packages.lines() {
state = PackageState::Uninstalled;
description = "[No description]";
description = "[No description] : CONTRIBUTION WELCOMED";
uad_list = UadList::Unlisted;
removal = Removal::Unlisted;
if uad_lists.contains_key(p_name) {
description = match &uad_lists.get(p_name).unwrap().description {
Some(descr) => descr,
None => "[No description]",
description = &uad_lists.get(p_name).unwrap().description;
if description.is_empty() {
description = "[No description] : CONTRIBUTION WELCOMED";
};
uad_list = uad_lists.get(p_name).unwrap().list;
removal = uad_lists.get(p_name).unwrap().removal;
@ -43,85 +43,94 @@ pub fn fetch_packages(
state = PackageState::Disabled;
}
let package_row = PackageRow::new(p_name, state, description, uad_list, removal, false);
let package_row =
PackageRow::new(p_name, state, description, uad_list, removal, false, false);
user_package.push(package_row);
}
user_package.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
user_package
}
pub fn update_selection_count(selection: &mut Selection, p_state: PackageState, add: bool) {
match p_state {
PackageState::Enabled => {
if add {
selection.enabled += 1
} else {
selection.enabled -= 1
};
}
PackageState::Disabled => {
if add {
selection.disabled += 1
} else {
selection.disabled -= 1
};
}
PackageState::Uninstalled => {
if add {
selection.uninstalled += 1
} else {
selection.uninstalled -= 1
};
}
PackageState::All => {}
};
}
pub async fn export_selection(packages: Vec<PackageRow>) -> Result<bool, String> {
let selected = packages
.iter()
.filter(|p| p.selected)
.map(|p| p.name.clone())
.collect::<Vec<String>>()
.join("\n");
match fs::write("uad_exported_selection.txt", selected) {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string()),
pub fn string_to_theme(theme: &str) -> Theme {
match theme {
"Dark" => Theme::Dark,
"Light" => Theme::Light,
"Lupin" => Theme::Lupin,
_ => Theme::Lupin,
}
}
#[allow(clippy::needless_collect)] // false positive: https://github.com/rust-lang/rust-clippy/issues/6164
pub fn import_selection(
packages: &mut Vec<PackageRow>,
selection: &mut Selection,
) -> io::Result<()> {
let file = fs::File::open("uad_exported_selection.txt")?;
let reader = BufReader::new(file);
let imported_selection: Vec<String> = reader
.lines()
.map(|l| l.expect("Could not parse line"))
.collect();
pub fn setup_uad_dir(dir: Option<PathBuf>) -> PathBuf {
let dir = dir.unwrap().join("uad");
fs::create_dir_all(&dir).expect("Can't create cache directory");
dir
}
*selection = Selection::default(); // should already be empty normally
pub fn open_url(dir: PathBuf) {
#[cfg(target_os = "windows")]
let output = Command::new("explorer").args([dir]).output();
for (i, p) in packages.iter_mut().enumerate() {
if imported_selection.contains(&p.name) {
p.selected = true;
selection.selected_packages.push(i);
update_selection_count(selection, p.state, true);
#[cfg(target_os = "macos")]
let output = Command::new("open").args([dir]).output();
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let output = Command::new("xdg-open").args([dir]).output();
match output {
Ok(o) => {
if !o.status.success() {
let stderr = String::from_utf8(o.stderr).unwrap().trim_end().to_string();
error!("Can't open the following URL: {}", stderr);
}
}
Err(e) => error!("Failed to run command to open the file explorer: {}", e),
}
}
#[rustfmt::skip]
#[allow(clippy::option_if_let_else)]
pub fn last_modified_date(file: PathBuf) -> DateTime<Utc> {
fs::metadata(file).map_or_else(|_| Utc::now(), |metadata| match metadata.modified() {
Ok(time) => time.into(),
Err(_) => Utc::now(),
})
}
pub fn format_diff_time_from_now(date: DateTime<Utc>) -> String {
let now: DateTime<Utc> = Utc::now();
let last_update = now - date;
if last_update.num_days() == 0 {
if last_update.num_hours() == 0 {
last_update.num_minutes().to_string() + " min(s) ago"
} else {
p.selected = false;
last_update.num_hours().to_string() + " hour(s) ago"
}
} else {
last_update.num_days().to_string() + " day(s) ago"
}
Ok(())
}
pub fn icon(unicode: char) -> Text {
Text::new(&unicode.to_string())
.font(ICONS)
.width(Length::Units(17))
.horizontal_alignment(alignment::Horizontal::Center)
.size(17)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DisplayablePath {
pub path: PathBuf,
}
impl fmt::Display for DisplayablePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stem = self.path.file_stem().map_or_else(
|| {
error!("[PATH STEM]: No file stem found");
"[File steam not found]".to_string()
},
|p| match p.to_os_string().into_string() {
Ok(stem) => stem,
Err(e) => {
error!("[PATH ENCODING]: {:?}", e);
"[PATH ENCODING ERROR]".to_string()
}
},
);
write!(f, "{stem}")
}
}

View file

@ -2,47 +2,51 @@ pub mod style;
pub mod views;
pub mod widgets;
pub use crate::core::sync::Phone;
pub use crate::core::uad_lists::Package;
use crate::core::utils::icon;
pub use views::about::About as AboutView;
pub use views::list::{List as AppsView, Message as AppsMessage};
pub use views::settings::{Message as SettingsMessage, Settings as SettingsView};
use crate::core::sync::{get_devices_list, perform_adb_commands, CommandType, Phone};
use crate::core::theme::Theme;
use crate::core::uad_lists::UadListState;
use crate::core::update::{get_latest_release, Release, SelfUpdateState, SelfUpdateStatus};
use crate::core::utils::string_to_theme;
use views::about::{About as AboutView, Message as AboutMessage};
use views::list::{List as AppsView, LoadingState as ListLoadingState, Message as AppsMessage};
use views::settings::{Message as SettingsMessage, Settings as SettingsView};
use widgets::navigation_menu::nav_menu;
use iced::widget::column;
use iced::{
button, window::Settings as Window, Alignment, Application, Button, Column, Command, Container,
Element, Font, Length, Row, Settings, Space, Text,
window::Settings as Window, Alignment, Application, Command, Element, Length, Renderer,
Settings,
};
use std::{env, path::PathBuf};
pub const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../ressources/assets/icons.ttf"),
};
#[cfg(feature = "self-update")]
use crate::core::update::{bin_name, download_update_to_temp_file, remove_file};
#[derive(Debug, Clone)]
pub enum View {
#[derive(Default, Debug, Clone)]
enum View {
#[default]
List,
About,
Settings,
}
impl Default for View {
fn default() -> Self {
Self::List
}
#[derive(Default, Clone)]
pub struct UpdateState {
self_update: SelfUpdateState,
uad_list: UadListState,
}
#[derive(Debug, Default)]
#[derive(Default, Clone)]
pub struct UadGui {
phone: Phone,
view: View,
apps_view: AppsView,
about_view: AboutView,
settings_view: SettingsView,
about_btn: button::State,
settings_btn: button::State,
apps_btn: button::State,
apps_refresh_btn: button::State,
devices_list: Vec<Phone>,
selected_device: Option<Phone>, // index of devices_list
update_state: UpdateState,
nb_running_async_adb_commands: u32,
}
#[derive(Debug, Clone)]
@ -50,15 +54,21 @@ pub enum Message {
// Navigation Panel
AboutPressed,
SettingsPressed,
AppsRefreshPress,
AppsPress,
DeviceSelected(Phone),
AboutAction(AboutMessage),
AppsAction(AppsMessage),
SettingsAction(SettingsMessage),
Init(AppsMessage),
RefreshButtonPressed,
RebootButtonPressed,
LoadDevices(Vec<Phone>),
_NewReleaseDownloaded(Result<(PathBuf, PathBuf), ()>),
GetLatestRelease(Result<Option<Release>, ()>),
Nothing,
}
impl Application for UadGui {
type Theme = Theme;
type Executor = iced::executor::Default;
type Message = Message;
type Flags = ();
@ -66,34 +76,45 @@ impl Application for UadGui {
fn new(_flags: ()) -> (Self, Command<Message>) {
(
Self::default(),
Command::perform(Self::load_phone_packages(), Message::Init),
Command::batch([
Command::perform(get_devices_list(), Message::LoadDevices),
Command::perform(
async move { get_latest_release() },
Message::GetLatestRelease,
),
]),
)
}
fn title(&self) -> String {
String::from("UadGui")
fn theme(&self) -> Theme {
string_to_theme(&self.settings_view.general.theme)
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Init(_) => {
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
);
Command::perform(Self::load_phone_packages(), Message::AppsAction)
}
Message::AppsRefreshPress => {
self.phone = Phone::default();
self.settings_view = SettingsView::default();
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
);
self.apps_view = AppsView::default();
self.view = View::List;
Command::perform(Self::load_phone_packages(), Message::AppsAction)
fn title(&self) -> String {
String::from("Universal Android Debloater")
}
fn update(&mut self, msg: Message) -> Command<Message> {
match msg {
#[allow(clippy::option_if_let_else)]
Message::LoadDevices(devices_list) => {
self.selected_device = match &self.selected_device {
Some(s_device) => {
// Try to reload last selected phone
devices_list
.iter()
.find(|phone| phone.adb_id == s_device.adb_id)
.cloned()
}
None => devices_list.first().cloned(),
};
self.devices_list = devices_list;
#[allow(unused_must_use)]
{
self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings));
}
self.update(Message::AppsAction(AppsMessage::LoadUadList(true)))
}
Message::AppsPress => {
self.view = View::List;
@ -101,100 +122,246 @@ impl Application for UadGui {
}
Message::AboutPressed => {
self.view = View::About;
Command::none()
self.update_state.self_update = SelfUpdateState::default();
Command::perform(
async move { get_latest_release() },
Message::GetLatestRelease,
)
}
Message::SettingsPressed => {
self.view = View::Settings;
Command::none()
}
Message::RefreshButtonPressed => {
self.apps_view = AppsView::default();
Command::perform(get_devices_list(), Message::LoadDevices)
}
Message::RebootButtonPressed => {
self.apps_view = AppsView::default();
self.selected_device = None;
self.devices_list = vec![];
Command::perform(
perform_adb_commands("reboot".to_string(), CommandType::Shell),
|_| Message::Nothing,
)
}
Message::AppsAction(msg) => self
.apps_view
.update(&self.settings_view, &mut self.phone, msg)
.update(
&mut self.settings_view,
&mut self.selected_device.clone().unwrap_or_default(),
&mut self.update_state.uad_list,
msg,
)
.map(Message::AppsAction),
Message::SettingsAction(msg) => {
self.settings_view.update(msg);
match msg {
SettingsMessage::RestoringDevice(ref output) => {
self.nb_running_async_adb_commands -= 1;
self.view = View::List;
#[allow(unused_must_use)]
{
self.apps_view.update(
&mut self.settings_view,
&mut self.selected_device.clone().unwrap_or_default(),
&mut self.update_state.uad_list,
AppsMessage::RestoringDevice(output.clone()),
);
}
if self.nb_running_async_adb_commands == 0 {
return self.update(Message::RefreshButtonPressed);
}
}
SettingsMessage::MultiUserMode(toggled) => {
if toggled {
for user in self.apps_view.phone_packages.clone() {
for (i, _) in
user.iter().enumerate().filter(|&(_, pkg)| pkg.selected)
{
for u in self
.selected_device
.as_ref()
.unwrap()
.user_list
.iter()
.filter(|&u| !u.protected)
{
self.apps_view.phone_packages[u.index][i].selected = true;
}
}
}
}
}
_ => (),
}
self.settings_view
.update(
&self.selected_device.clone().unwrap_or_default(),
&self.apps_view.phone_packages,
&mut self.nb_running_async_adb_commands,
msg,
)
.map(Message::SettingsAction)
}
Message::AboutAction(msg) => {
self.about_view.update(msg.clone());
match msg {
AboutMessage::UpdateUadLists => {
self.update_state.uad_list = UadListState::Downloading;
self.apps_view.loading_state =
ListLoadingState::DownloadingList(String::new());
self.update(Message::AppsAction(AppsMessage::LoadUadList(true)))
}
AboutMessage::DoSelfUpdate => {
#[cfg(feature = "self-update")]
if self.update_state.self_update.latest_release.is_some() {
self.update_state.self_update.status = SelfUpdateStatus::Updating;
self.apps_view.loading_state =
ListLoadingState::_UpdatingUad(String::new());
let bin_name = bin_name().to_owned();
let release = self
.update_state
.self_update
.latest_release
.as_ref()
.unwrap()
.clone();
Command::perform(
download_update_to_temp_file(bin_name, release),
Message::_NewReleaseDownloaded,
)
} else {
Command::none()
}
#[cfg(not(feature = "self-update"))]
Command::none()
}
AboutMessage::UrlPressed(_) => Command::none(),
}
}
Message::DeviceSelected(s_device) => {
self.selected_device = Some(s_device.clone());
self.view = View::List;
env::set_var("ANDROID_SERIAL", s_device.adb_id);
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | DEVICE: {}",
s_device.android_sdk, s_device.model
);
info!("{:-^65}", "-");
self.apps_view.loading_state = ListLoadingState::FindingPhones(String::new());
#[allow(unused_must_use)]
{
self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings));
}
self.update(Message::AppsAction(AppsMessage::LoadPhonePackages((
self.apps_view.uad_lists.clone(),
UadListState::Done,
))))
}
Message::_NewReleaseDownloaded(res) => {
debug!("UAD update has been download!");
#[cfg(feature = "self-update")]
if let Ok((relaunch_path, cleanup_path)) = res {
// Remove first arg, which is path to binary. We don't use this first
// arg as binary path because it's not reliable, per the docs.
let mut args = std::env::args();
args.next();
let mut args: Vec<_> = args.collect();
// Remove the `--self-update-temp` arg from args if it exists,
// since we need to pass it cleanly. Otherwise new process will
// fail during arg parsing.
if let Some(idx) = args.iter().position(|a| a == "--self-update-temp") {
args.remove(idx);
// Remove path passed after this arg
args.remove(idx);
}
match std::process::Command::new(relaunch_path)
.args(args)
.arg("--self-update-temp")
.arg(&cleanup_path)
.spawn()
{
Ok(_) => {
if let Err(e) = remove_file(cleanup_path) {
error!("Could not remove temp update file: {}", e);
}
std::process::exit(0)
}
Err(error) => {
if let Err(e) = remove_file(cleanup_path) {
error!("Could not remove temp update file: {}", e);
}
error!("Failed to update UAD: {}", error);
}
}
} else {
error!("Failed to update UAD!");
}
Command::none()
}
Message::GetLatestRelease(release) => {
match release {
Ok(r) => {
self.update_state.self_update.status = SelfUpdateStatus::Done;
self.update_state.self_update.latest_release = r;
}
Err(_) => self.update_state.self_update.status = SelfUpdateStatus::Failed,
};
Command::none()
}
Message::Nothing => Command::none(),
}
}
fn view(&mut self) -> Element<Message> {
let apps_btn = Button::new(&mut self.apps_btn, Text::new("Apps"))
.on_press(Message::AppsPress)
.padding(5)
.style(style::PrimaryButton(self.settings_view.theme.palette));
let apps_refresh_btn = Button::new(&mut self.apps_refresh_btn, refresh_icon())
.on_press(Message::AppsRefreshPress)
.padding(5)
.style(style::RefreshButton(self.settings_view.theme.palette));
let uad_version = Text::new(env!("CARGO_PKG_VERSION"));
let about_btn = Button::new(&mut self.about_btn, Text::new("About"))
.on_press(Message::AboutPressed)
.padding(5)
.style(style::PrimaryButton(self.settings_view.theme.palette));
let settings_btn = Button::new(&mut self.settings_btn, Text::new("Settings"))
.on_press(Message::SettingsPressed)
.padding(5)
.style(style::PrimaryButton(self.settings_view.theme.palette));
let row = Row::new()
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10)
.push(apps_refresh_btn)
.push(Text::new("Device: ".to_string() + &self.phone.model))
.push(Space::new(Length::Fill, Length::Shrink))
.push(uad_version)
.push(apps_btn)
.push(about_btn)
.push(settings_btn);
let navigation_container = Container::new(row)
.width(Length::Fill)
.padding(10)
.style(style::NavigationContainer(self.settings_view.theme.palette));
fn view(&self) -> Element<Self::Message, Renderer<Self::Theme>> {
let navigation_container = nav_menu(
&self.devices_list,
self.selected_device.clone(),
&self.apps_view,
&self.update_state.self_update,
);
let selected_device = self.selected_device.clone().unwrap_or_default();
let main_container = match self.view {
View::List => self
.apps_view
.view(&self.settings_view, &self.phone)
.view(&self.settings_view, &selected_device)
.map(Message::AppsAction),
View::About => self.about_view.view(&self.settings_view),
View::Settings => self.settings_view.view().map(Message::SettingsAction),
View::About => self
.about_view
.view(&self.update_state)
.map(Message::AboutAction),
View::Settings => self
.settings_view
.view(&selected_device)
.map(Message::SettingsAction),
};
Column::new()
column![navigation_container, main_container]
.width(Length::Fill)
.push(navigation_container)
.push(main_container)
.align_items(Alignment::Center)
.into()
}
}
impl UadGui {
pub fn start() {
let settings: Settings<()> = Settings {
pub fn start() -> iced::Result {
Self::run(Settings {
window: Window {
size: (1050, 800),
resizable: true,
decorations: true,
..iced::window::Settings::default()
},
default_text_size: 17,
..iced::Settings::default()
};
Self::run(settings).unwrap_err();
}
pub async fn load_phone_packages() -> AppsMessage {
AppsMessage::LoadPackages
default_text_size: 17.0,
..Settings::default()
})
}
}
fn refresh_icon() -> Text {
icon('\u{E900}')
}

View file

@ -1,395 +1,479 @@
use crate::core::theme::ColorPalette;
use iced::{button, checkbox, container, pick_list, scrollable, text_input, Background, Color};
use crate::core::theme::Theme;
use iced::overlay::menu;
use iced::widget::{
button, checkbox, container, pick_list, radio, rule, scrollable, text, text_input,
};
use iced::{application, Background, Color};
pub struct Content(pub ColorPalette);
impl container::StyleSheet for Content {
fn style(&self) -> container::Style {
container::Style {
background: Some(Background::Color(self.0.base.background)),
text_color: Some(self.0.bright.surface),
..container::Style::default()
#[derive(Default, Debug, Clone, Copy)]
pub enum Application {
#[default]
Default,
}
impl application::StyleSheet for Theme {
type Style = Application;
fn appearance(&self, _style: &Self::Style) -> application::Appearance {
application::Appearance {
background_color: self.palette().base.background,
text_color: self.palette().bright.surface,
}
}
}
pub struct NavigationContainer(pub ColorPalette);
impl container::StyleSheet for NavigationContainer {
fn style(&self) -> container::Style {
container::Style {
background: Some(Background::Color(self.0.base.foreground)),
text_color: Some(self.0.bright.surface),
..container::Style::default()
}
}
#[derive(Default, Debug, Clone, Copy)]
pub enum Container {
#[default]
Invisible,
Frame,
BorderedFrame,
Tooltip,
Background,
}
pub struct PrimaryButton(pub ColorPalette);
impl button::StyleSheet for PrimaryButton {
fn active(&self) -> button::Style {
button::Style {
border_color: Color {
a: 0.5,
..self.0.bright.primary
impl container::StyleSheet for Theme {
type Style = Container;
fn appearance(&self, style: &Self::Style) -> container::Appearance {
match style {
Container::Invisible => container::Appearance::default(),
Container::Frame => container::Appearance {
background: Some(Background::Color(self.palette().base.foreground)),
text_color: Some(self.palette().bright.surface),
border_radius: 5.0,
..container::Appearance::default()
},
Container::BorderedFrame => container::Appearance {
background: Some(Background::Color(self.palette().base.foreground)),
text_color: Some(self.palette().bright.surface),
border_radius: 5.0,
border_width: 1.0,
border_color: self.palette().normal.error,
},
Container::Tooltip => container::Appearance {
background: Some(Background::Color(self.palette().base.foreground)),
text_color: Some(self.palette().bright.surface),
border_radius: 8.0,
border_width: 1.0,
border_color: self.palette().normal.primary,
},
Container::Background => container::Appearance {
background: Some(Background::Color(self.palette().base.background)),
text_color: Some(self.palette().bright.surface),
border_radius: 5.0,
..container::Appearance::default()
},
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Button {
#[default]
Primary,
Unavailable,
SelfUpdate,
Refresh,
UninstallPackage,
RestorePackage,
NormalPackage,
SelectedPackage,
}
impl button::StyleSheet for Theme {
type Style = Button;
fn active(&self, style: &Self::Style) -> button::Appearance {
let p = self.palette();
let appearance = button::Appearance {
border_width: 1.0,
border_radius: 2.0,
text_color: self.0.bright.primary,
..button::Style::default()
}
}
..button::Appearance::default()
};
fn hovered(&self) -> button::Style {
button::Style {
background: Some(Background::Color(Color {
a: 0.25,
..self.0.normal.surface
})),
text_color: self.0.bright.primary,
..self.active()
}
}
}
let active_appearance = |bg: Option<Color>, mc| button::Appearance {
background: Some(Background::Color(bg.unwrap_or(p.base.foreground))),
border_color: Color { a: 0.5, ..mc },
text_color: mc,
..appearance
};
pub struct RefreshButton(pub ColorPalette);
impl button::StyleSheet for RefreshButton {
fn active(&self) -> button::Style {
button::Style {
border_color: Color {
a: 0.5,
..self.0.bright.primary
match style {
Button::Primary | Button::SelfUpdate | Button::Refresh => {
active_appearance(None, p.bright.primary)
}
Button::RestorePackage => active_appearance(None, p.bright.secondary),
Button::NormalPackage => button::Appearance {
background: Some(Background::Color(p.base.foreground)),
text_color: p.bright.surface,
border_radius: 5.0,
border_width: 0.0,
border_color: p.base.background,
..appearance
},
border_width: 1.0,
border_radius: 2.0,
text_color: self.0.bright.primary,
..button::Style::default()
}
}
fn hovered(&self) -> button::Style {
button::Style {
background: Some(Background::Color(Color {
a: 0.25,
..self.0.normal.surface
})),
text_color: self.0.bright.primary,
..self.active()
}
}
}
pub enum PackageButton {
Uninstall(ColorPalette),
Restore(ColorPalette),
}
impl button::StyleSheet for PackageButton {
fn active(&self) -> button::Style {
match self {
Self::Uninstall(palette) => button::Style {
border_color: Color {
a: 0.5,
..palette.bright.error
},
border_width: 1.0,
border_radius: 2.0,
text_color: palette.bright.error,
..button::Style::default()
},
Self::Restore(palette) => button::Style {
border_color: Color {
a: 0.5,
..palette.bright.secondary
},
border_width: 1.0,
border_radius: 2.0,
text_color: palette.bright.secondary,
..button::Style::default()
},
}
}
fn hovered(&self) -> button::Style {
match self {
Self::Restore(palette) => button::Style {
Button::SelectedPackage => button::Appearance {
background: Some(Background::Color(Color {
a: 0.25,
..palette.normal.primary
..p.normal.primary
})),
text_color: palette.bright.primary,
..self.active()
},
Self::Uninstall(palette) => button::Style {
background: Some(Background::Color(Color {
a: 0.25,
..palette.normal.primary
})),
text_color: palette.bright.primary,
..self.active()
text_color: p.bright.primary,
border_radius: 5.0,
border_width: 0.0,
border_color: p.normal.primary,
..appearance
},
Button::Unavailable | Button::UninstallPackage => {
active_appearance(None, p.bright.error)
}
}
}
fn disabled(&self) -> button::Style {
match self {
Self::Restore(palette) => button::Style {
background: Some(Background::Color(Color {
a: 0.05,
..palette.normal.primary
})),
text_color: Color {
a: 0.50,
..palette.bright.primary
},
..self.active()
},
Self::Uninstall(palette) => button::Style {
background: Some(Background::Color(Color {
a: 0.01,
..palette.bright.error
})),
text_color: Color {
a: 0.50,
..palette.bright.error
},
..self.active()
fn hovered(&self, style: &Self::Style) -> button::Appearance {
let active = self.active(style);
let p = self.palette();
let hover_appearance = |bg, tc: Option<Color>| button::Appearance {
background: Some(Background::Color(Color { a: 0.25, ..bg })),
text_color: tc.unwrap_or(bg),
..active
};
match style {
Button::Primary | Button::SelfUpdate | Button::Refresh => {
hover_appearance(p.bright.primary, None)
}
Button::NormalPackage => hover_appearance(p.normal.primary, Some(p.bright.surface)),
Button::SelectedPackage => hover_appearance(p.normal.primary, None),
Button::RestorePackage => hover_appearance(p.bright.secondary, None),
Button::Unavailable | Button::UninstallPackage => {
hover_appearance(p.bright.error, None)
}
}
}
fn pressed(&self, style: &Self::Style) -> button::Appearance {
self.active(style)
}
fn disabled(&self, style: &Self::Style) -> button::Appearance {
let active = self.active(style);
let p = self.palette();
let disabled_appearance = |bg, tc: Option<Color>| button::Appearance {
background: Some(Background::Color(Color { a: 0.05, ..bg })),
text_color: Color {
a: 0.50,
..tc.unwrap_or(bg)
},
..active
};
match style {
Button::RestorePackage => disabled_appearance(p.normal.primary, Some(p.bright.primary)),
Button::UninstallPackage => disabled_appearance(p.bright.error, None),
Button::Primary => disabled_appearance(p.bright.primary, Some(p.bright.primary)),
_ => active,
}
}
}
pub struct PackageRow(pub ColorPalette);
impl button::StyleSheet for PackageRow {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(self.0.base.foreground)),
text_color: self.0.bright.surface,
border_radius: 5.0,
border_width: 0.0,
border_color: self.0.base.background,
..button::Style::default()
}
}
fn hovered(&self) -> button::Style {
button::Style {
background: Some(Background::Color(Color {
a: 0.25,
..self.0.normal.primary
})),
text_color: self.0.bright.primary,
..self.active()
}
}
fn pressed(&self) -> button::Style {
button::Style {
background: Some(Background::Color(Color::from_rgb(0.35, 0.43, 0.46))),
text_color: self.0.bright.primary,
..self.active()
}
}
#[derive(Default, Debug, Clone, Copy)]
pub enum Scrollable {
#[default]
Description,
Packages,
}
pub struct Description(pub ColorPalette);
impl container::StyleSheet for Description {
fn style(&self) -> container::Style {
container::Style {
background: Some(Background::Color(self.0.base.foreground)),
text_color: Some(self.0.bright.surface),
border_radius: 5.0,
border_width: 0.0,
border_color: self.0.base.background,
}
}
}
impl scrollable::StyleSheet for Theme {
type Style = Scrollable;
pub struct DescriptionScrollable(pub ColorPalette);
impl scrollable::StyleSheet for DescriptionScrollable {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
background: Some(Background::Color(self.0.base.foreground)),
fn active(&self, style: &Self::Style) -> scrollable::Scrollbar {
let from_appearance = |c: Color| scrollable::Scrollbar {
background: Some(Background::Color(Color::TRANSPARENT)),
border_radius: 5.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: self.0.base.foreground,
border_radius: 0.0,
border_width: 0.0,
color: c,
border_radius: 5.0,
border_width: 1.0,
border_color: Color::TRANSPARENT,
},
};
match style {
Scrollable::Description => from_appearance(self.palette().normal.surface),
Scrollable::Packages => from_appearance(self.palette().base.foreground),
}
}
fn hovered(&self) -> scrollable::Scrollbar {
let active = self.active();
fn hovered(&self, style: &Self::Style, _mouse_over_scrollbar: bool) -> scrollable::Scrollbar {
scrollable::Scrollbar {
scroller: scrollable::Scroller { ..active.scroller },
..active
scroller: self.active(style).scroller,
..self.active(style)
}
}
fn dragging(&self) -> scrollable::Scrollbar {
let hovered = self.hovered();
fn dragging(&self, style: &Self::Style) -> scrollable::Scrollbar {
let hovered = self.hovered(style, true);
scrollable::Scrollbar {
scroller: scrollable::Scroller { ..hovered.scroller },
scroller: hovered.scroller,
..hovered
}
}
}
pub struct Scrollable(pub ColorPalette);
impl scrollable::StyleSheet for Scrollable {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
background: Some(Background::Color(self.0.base.background)),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: self.0.base.foreground,
border_radius: 2.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
}
fn hovered(&self) -> scrollable::Scrollbar {
let active = self.active();
scrollable::Scrollbar {
scroller: scrollable::Scroller { ..active.scroller },
..active
}
}
fn dragging(&self) -> scrollable::Scrollbar {
let hovered = self.hovered();
scrollable::Scrollbar {
scroller: scrollable::Scroller { ..hovered.scroller },
..hovered
}
}
#[derive(Default, Debug, Clone, Copy)]
pub enum CheckBox {
#[default]
PackageEnabled,
PackageDisabled,
SettingsEnabled,
SettingsDisabled,
}
pub enum SelectionCheckBox {
Enabled(ColorPalette),
Disabled(ColorPalette),
}
impl checkbox::StyleSheet for Theme {
type Style = CheckBox;
impl checkbox::StyleSheet for SelectionCheckBox {
fn active(&self, _is_checked: bool) -> checkbox::Style {
match self {
Self::Enabled(palette) => checkbox::Style {
background: Background::Color(palette.base.background),
checkmark_color: palette.bright.primary,
fn active(&self, style: &Self::Style, _is_checked: bool) -> checkbox::Appearance {
match style {
CheckBox::PackageEnabled => checkbox::Appearance {
background: Background::Color(self.palette().base.background),
icon_color: self.palette().bright.primary,
border_radius: 5.0,
border_width: 1.0,
border_color: palette.base.background,
border_color: self.palette().base.background,
text_color: Some(self.palette().bright.surface),
},
Self::Disabled(palette) => checkbox::Style {
background: Background::Color(palette.base.foreground),
checkmark_color: palette.bright.primary,
CheckBox::PackageDisabled => checkbox::Appearance {
background: Background::Color(Color {
a: 0.55,
..self.palette().base.background
}),
icon_color: self.palette().bright.primary,
border_radius: 5.0,
border_width: 1.0,
border_color: palette.normal.primary,
border_color: self.palette().normal.primary,
text_color: Some(self.palette().normal.primary),
},
CheckBox::SettingsEnabled => checkbox::Appearance {
background: Background::Color(self.palette().base.background),
icon_color: self.palette().bright.primary,
border_radius: 5.0,
border_width: 1.0,
border_color: self.palette().bright.primary,
text_color: Some(self.palette().bright.surface),
},
CheckBox::SettingsDisabled => checkbox::Appearance {
background: Background::Color(self.palette().base.foreground),
icon_color: self.palette().bright.primary,
border_radius: 5.0,
border_width: 1.0,
border_color: self.palette().normal.primary,
text_color: Some(self.palette().bright.surface),
},
}
}
fn hovered(&self, is_checked: bool) -> checkbox::Style {
match self {
Self::Enabled(palette) => checkbox::Style {
background: Background::Color(palette.base.foreground),
checkmark_color: palette.bright.primary,
border_radius: 5.0,
border_width: 2.0,
border_color: palette.normal.primary,
},
fn hovered(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance {
let from_appearance = || checkbox::Appearance {
background: Background::Color(self.palette().base.foreground),
icon_color: self.palette().bright.primary,
border_radius: 5.0,
border_width: 2.0,
border_color: self.palette().bright.primary,
text_color: Some(self.palette().bright.surface),
};
Self::Disabled(_) => checkbox::Style {
..self.active(is_checked)
},
match style {
CheckBox::PackageEnabled | CheckBox::SettingsEnabled => from_appearance(),
CheckBox::PackageDisabled | CheckBox::SettingsDisabled => {
self.active(style, is_checked)
}
}
}
}
pub struct SearchInput(pub ColorPalette);
impl text_input::StyleSheet for SearchInput {
fn active(&self) -> text_input::Style {
text_input::Style {
background: Background::Color(self.0.base.foreground),
#[derive(Default, Debug, Clone, Copy)]
pub enum TextInput {
#[default]
Default,
}
impl text_input::StyleSheet for Theme {
type Style = TextInput;
fn active(&self, _style: &Self::Style) -> text_input::Appearance {
text_input::Appearance {
background: Background::Color(self.palette().base.foreground),
border_radius: 5.0,
border_width: 0.0,
border_color: self.0.base.foreground,
border_color: self.palette().base.foreground,
}
}
fn focused(&self) -> text_input::Style {
text_input::Style {
background: Background::Color(self.0.base.foreground),
fn focused(&self, _style: &Self::Style) -> text_input::Appearance {
text_input::Appearance {
background: Background::Color(self.palette().base.foreground),
border_radius: 2.0,
border_width: 1.0,
border_color: Color {
a: 0.5,
..self.0.normal.primary
..self.palette().normal.primary
},
}
}
fn placeholder_color(&self) -> Color {
self.0.normal.surface
fn placeholder_color(&self, _style: &Self::Style) -> Color {
self.palette().normal.surface
}
fn value_color(&self) -> Color {
self.0.bright.primary
fn value_color(&self, _style: &Self::Style) -> Color {
self.palette().bright.primary
}
fn selection_color(&self) -> Color {
self.0.bright.secondary
fn selection_color(&self, _style: &Self::Style) -> Color {
self.palette().normal.primary
}
/// Produces the style of an hovered text input.
fn hovered(&self) -> text_input::Style {
self.focused()
fn hovered(&self, style: &Self::Style) -> text_input::Appearance {
self.focused(style)
}
}
pub struct PickList(pub ColorPalette);
impl pick_list::StyleSheet for PickList {
fn menu(&self) -> pick_list::Menu {
pick_list::Menu {
text_color: self.0.bright.surface,
background: Background::Color(self.0.base.foreground),
#[derive(Default, Debug, Clone, Copy)]
pub enum PickList {
#[default]
Default,
}
impl menu::StyleSheet for Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> menu::Appearance {
let p = self.palette();
menu::Appearance {
text_color: p.bright.surface,
background: p.base.background.into(),
border_width: 1.0,
border_color: self.0.base.background,
selected_background: Background::Color(Color {
a: 0.15,
..self.0.normal.primary
}),
selected_text_color: self.0.bright.primary,
border_radius: 2.0,
border_color: p.base.background,
selected_text_color: p.bright.surface,
selected_background: p.normal.primary.into(),
}
}
}
fn active(&self) -> pick_list::Style {
pick_list::Style {
text_color: self.0.bright.surface,
background: self.0.base.background.into(),
impl pick_list::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &()) -> pick_list::Appearance {
pick_list::Appearance {
text_color: self.palette().bright.surface,
background: self.palette().base.background.into(),
border_width: 1.0,
border_color: Color {
a: 0.5,
..self.0.normal.primary
..self.palette().normal.primary
},
border_radius: 2.0,
icon_size: 0.5,
placeholder_color: self.0.bright.surface,
handle_color: self.palette().bright.surface,
placeholder_color: self.palette().bright.surface,
}
}
fn hovered(&self) -> pick_list::Style {
let active = self.active();
pick_list::Style {
text_color: self.0.bright.primary,
fn hovered(&self, style: &()) -> pick_list::Appearance {
let active = self.active(style);
pick_list::Appearance {
border_color: self.palette().normal.primary,
..active
}
}
}
#[derive(Default, Clone, Copy)]
pub enum Text {
#[default]
Default,
Ok,
Danger,
Commentary,
Color(Color),
}
impl From<Color> for Text {
fn from(color: Color) -> Self {
Self::Color(color)
}
}
impl text::StyleSheet for Theme {
type Style = Text;
fn appearance(&self, style: Self::Style) -> text::Appearance {
match style {
Text::Default => text::Appearance::default(),
Text::Ok => text::Appearance {
color: Some(self.palette().bright.secondary),
},
Text::Danger => text::Appearance {
color: Some(self.palette().bright.error),
},
Text::Commentary => text::Appearance {
color: Some(self.palette().normal.surface),
},
Text::Color(c) => text::Appearance { color: Some(c) },
}
}
}
impl radio::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &Self::Style, _is_selected: bool) -> radio::Appearance {
radio::Appearance {
background: Color::TRANSPARENT.into(),
dot_color: self.palette().bright.primary,
border_width: 1.0,
border_color: self.palette().bright.primary,
text_color: None,
}
}
fn hovered(&self, style: &Self::Style, _is_selected: bool) -> radio::Appearance {
let active = self.active(style, true);
radio::Appearance {
dot_color: self.palette().bright.primary,
border_color: self.palette().bright.primary,
border_width: 2.0,
..active
}
}
}
#[derive(Default, Clone, Copy)]
pub enum Rule {
#[default]
Default,
}
impl rule::StyleSheet for Theme {
type Style = Rule;
fn appearance(&self, style: &Self::Style) -> rule::Appearance {
match style {
Rule::Default => rule::Appearance {
color: self.palette().bright.surface,
width: 2,
radius: 2.0,
fill_mode: rule::FillMode::Full,
},
}
}
}

View file

@ -1,28 +1,145 @@
use crate::gui::style;
use crate::gui::views::settings::Settings;
use crate::gui::Message;
use iced::{Alignment, Column, Container, Element, Length, Text};
use crate::core::theme::Theme;
use crate::core::utils::{last_modified_date, open_url};
use crate::gui::{style, UpdateState};
use crate::CACHE_DIR;
use iced::widget::{button, column, container, row, text, Space};
use iced::{Alignment, Element, Length, Renderer};
use std::path::PathBuf;
#[cfg(feature = "self-update")]
use crate::core::update::SelfUpdateStatus;
#[derive(Default, Debug, Clone)]
pub struct About {}
#[derive(Debug, Clone)]
pub enum Message {
UrlPressed(PathBuf),
UpdateUadLists,
DoSelfUpdate,
}
impl About {
pub fn view(&self, settings: &Settings) -> Element<Message> {
let about_text = Text::new(
"Welcome to UadGui, a completely free project that helps you remove pre-installed apps on your Android device.",
pub fn update(&mut self, msg: Message) {
if let Message::UrlPressed(url) = msg {
open_url(url);
}
// other events are handled by UadGui update()
}
pub fn view(&self, update_state: &UpdateState) -> Element<Message, Renderer<Theme>> {
let about_text = text(
"Universal Android Debloater (UAD) is a Free and Open-Source community project aiming at simplifying \
the removal of pre-installed apps on any Android device.",
);
let content = Column::new()
let descr_container = container(about_text)
.width(Length::Fill)
.spacing(10)
.align_items(Alignment::Center)
.push(about_text);
.padding(25)
.style(style::Container::Frame);
Container::new(content)
let date = last_modified_date(CACHE_DIR.join("uad_lists.json"));
let uad_list_text = text(format!("Documentation: v{}", date.format("%Y%m%d"))).width(250);
let last_update_text = text(update_state.uad_list.to_string());
let uad_lists_btn = button("Update")
.on_press(Message::UpdateUadLists)
.padding(5)
.style(style::Button::Primary);
#[cfg(feature = "self-update")]
let self_update_btn = button("Update")
.on_press(Message::DoSelfUpdate)
.padding(5)
.style(style::Button::Primary);
#[cfg(feature = "self-update")]
let uad_version_text =
text(format!("UAD version: v{}", env!("CARGO_PKG_VERSION"))).width(250);
#[cfg(feature = "self-update")]
#[rustfmt::skip]
let self_update_text = update_state.self_update.latest_release.as_ref().map_or_else(||
if update_state.self_update.status == SelfUpdateStatus::Done {
"(No update available)".to_string()
} else {
update_state.self_update.status.to_string()
}, |r| if update_state.self_update.status == SelfUpdateStatus::Updating {
update_state.self_update.status.to_string()
} else {
format!("(v{} available)", r.tag_name)
});
#[cfg(feature = "self-update")]
let last_self_update_text = text(self_update_text).style(style::Text::Default);
#[cfg(feature = "self-update")]
let self_update_row = row![uad_version_text, self_update_btn, last_self_update_text,]
.align_items(Alignment::Center)
.spacing(10)
.width(550);
let uad_list_row = row![uad_list_text, uad_lists_btn, last_update_text,]
.align_items(Alignment::Center)
.spacing(10)
.width(550);
#[cfg(feature = "self-update")]
let update_column = column![uad_list_row, self_update_row]
.align_items(Alignment::Center)
.spacing(10);
#[cfg(not(feature = "self-update"))]
let update_column = column![uad_list_row]
.align_items(Alignment::Center)
.spacing(10);
let update_container = container(update_column)
.width(Length::Fill)
.center_x()
.padding(10)
.style(style::Container::Frame);
let website_btn = button("Github page")
.on_press(Message::UrlPressed(PathBuf::from(
"https://github.com/0x192/universal-android-debloater",
)))
.padding(5)
.style(style::Button::Primary);
let issue_btn = button("Have an issue?")
.on_press(Message::UrlPressed(PathBuf::from(
"https://github.com/0x192/universal-android-debloater/issues",
)))
.padding(5)
.style(style::Button::Primary);
let log_btn = button("Locate the logfiles")
.on_press(Message::UrlPressed(CACHE_DIR.to_path_buf()))
.padding(5)
.style(style::Button::Primary);
let wiki_btn = button("Wiki")
.on_press(Message::UrlPressed(PathBuf::from(
"https://github.com/0x192/universal-android-debloater/wiki",
)))
.padding(5)
.style(style::Button::Primary);
let row = row![website_btn, wiki_btn, issue_btn, log_btn,].spacing(20);
let content = column![
Space::new(Length::Fill, Length::Shrink),
descr_container,
update_container,
row,
]
.width(Length::Fill)
.spacing(20)
.align_items(Alignment::Center);
container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(10)
.style(style::Content(settings.theme.palette))
.into()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,134 +1,427 @@
use crate::core::sync::get_android_sdk;
use crate::core::config::{BackupSettings, Config, DeviceSettings, GeneralSettings};
use crate::core::save::{
backup_phone, list_available_backup_user, list_available_backups, restore_backup, BACKUP_DIR,
};
use crate::core::sync::{get_android_sdk, perform_adb_commands, CommandType, Phone};
use crate::core::theme::Theme;
use crate::core::utils::{open_url, string_to_theme, DisplayablePath};
use crate::gui::style;
use iced::{pick_list, Checkbox, Column, Container, Element, Length, PickList, Space, Text};
use crate::gui::views::list::PackageInfo;
use crate::gui::widgets::package_row::PackageRow;
use iced::widget::{button, checkbox, column, container, pick_list, radio, row, text, Space};
use iced::{alignment, Alignment, Command, Element, Length, Renderer};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct Settings {
pub expert_mode: bool,
pub disable_mode: bool,
pub multi_user_mode: bool,
pub theme: Theme,
theme_picklist: pick_list::State<Theme>,
pub general: GeneralSettings,
pub device: DeviceSettings,
}
impl Default for Settings {
fn default() -> Self {
Self {
expert_mode: false,
disable_mode: get_android_sdk() < 26,
multi_user_mode: get_android_sdk() > 21,
theme: Theme::lupin(),
theme_picklist: pick_list::State::default(),
general: Config::load_configuration_file().general,
device: DeviceSettings::default(),
}
}
}
#[derive(Debug, Clone)]
pub enum Message {
LoadDeviceSettings,
ExpertMode(bool),
DisableMode(bool),
MultiUserMode(bool),
ApplyTheme(Theme),
UrlPressed(PathBuf),
BackupSelected(DisplayablePath),
BackupDevice,
RestoreDevice,
RestoringDevice(Result<CommandType, ()>),
DeviceBackedUp(Result<(), String>),
}
impl Settings {
pub fn update(&mut self, msg: Message) {
pub fn update(
&mut self,
phone: &Phone,
packages: &[Vec<PackageRow>],
nb_running_async_adb_commands: &mut u32,
msg: Message,
) -> Command<Message> {
match msg {
Message::ExpertMode(toggled) => {
info!(
"Expert mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.expert_mode = toggled;
self.general.expert_mode = toggled;
debug!("Config change: {:?}", self);
Config::save_changes(self, &phone.adb_id);
Command::none()
}
Message::DisableMode(toggled) => {
info!(
"Disable mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.disable_mode = toggled;
if phone.android_sdk >= 23 {
self.device.disable_mode = toggled;
debug!("Config change: {:?}", self);
Config::save_changes(self, &phone.adb_id);
}
Command::none()
}
Message::MultiUserMode(toggled) => {
info!(
"Multi-user mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.multi_user_mode = toggled;
self.device.multi_user_mode = toggled;
debug!("Config change: {:?}", self);
Config::save_changes(self, &phone.adb_id);
Command::none()
}
Message::ApplyTheme(theme) => {
self.theme = theme;
self.general.theme = theme.to_string();
debug!("Config change: {:?}", self);
Config::save_changes(self, &phone.adb_id);
Command::none()
}
Message::UrlPressed(url) => {
open_url(url);
Command::none()
}
Message::LoadDeviceSettings => {
let backups = list_available_backups(&BACKUP_DIR.join(phone.adb_id.clone()));
match Config::load_configuration_file()
.devices
.iter()
.find(|d| d.device_id == phone.adb_id)
{
Some(device) => {
self.device = device.clone();
self.device.backup = BackupSettings {
backups: backups.clone(),
selected: backups.first().cloned(),
users: phone.user_list.clone(),
selected_user: phone.user_list.first().copied(),
backup_state: String::new(),
};
}
None => {
self.device = DeviceSettings {
device_id: phone.adb_id.clone(),
multi_user_mode: phone.android_sdk > 21,
disable_mode: false,
backup: BackupSettings {
backups: backups.clone(),
selected: backups.first().cloned(),
users: phone.user_list.clone(),
selected_user: phone.user_list.first().copied(),
backup_state: String::new(),
},
}
}
};
Command::none()
}
Message::BackupSelected(d_path) => {
self.device.backup.selected = Some(d_path.clone());
self.device.backup.users = list_available_backup_user(d_path);
Command::none()
}
Message::BackupDevice => Command::perform(
backup_phone(
phone.user_list.clone(),
self.device.device_id.clone(),
packages.to_vec(),
),
Message::DeviceBackedUp,
),
Message::DeviceBackedUp(_) => {
info!("[BACKUP] Backup successfully created");
self.device.backup.backups =
list_available_backups(&BACKUP_DIR.join(phone.adb_id.clone()));
self.device.backup.selected = self.device.backup.backups.first().cloned();
Command::none()
}
Message::RestoreDevice => match restore_backup(phone, packages, &self.device) {
Ok(r_packages) => {
let mut commands = vec![];
*nb_running_async_adb_commands = 0;
for p in &r_packages {
let p_info = PackageInfo {
i_user: 0,
index: p.index,
removal: "RESTORE".to_string(),
};
for command in p.commands.clone() {
*nb_running_async_adb_commands += 1;
commands.push(Command::perform(
perform_adb_commands(
command,
CommandType::PackageManager(p_info.clone()),
),
Message::RestoringDevice,
));
}
}
if r_packages.is_empty() {
if get_android_sdk() == 0 {
self.device.backup.backup_state = "Device is not connected".to_string();
} else {
self.device.backup.backup_state =
"Device state is already restored".to_string();
}
}
info!(
"[RESTORE] Restoring backup {}",
self.device.backup.selected.as_ref().unwrap()
);
Command::batch(commands)
}
Err(e) => {
self.device.backup.backup_state = e.to_string();
error!("{} - {}", self.device.backup.selected.as_ref().unwrap(), e);
Command::none()
}
},
// Trigger an action in mod.rs (Message::SettingsAction(msg))
Message::RestoringDevice(_) => Command::none(),
}
}
pub fn view(&mut self) -> Element<Message> {
let general_category_text = Text::new("General").size(25);
pub fn view(&self, phone: &Phone) -> Element<Message, Renderer<Theme>> {
let radio_btn_theme = Theme::ALL
.iter()
.fold(row![].spacing(10), |column, option| {
column.push(
radio(
format!("{}", option.clone()),
*option,
Some(string_to_theme(&self.general.theme)),
Message::ApplyTheme,
)
.size(23),
)
});
let theme_ctn = container(radio_btn_theme)
.padding(10)
.width(Length::Fill)
.height(Length::Shrink)
.style(style::Container::Frame);
let theme_picklist = PickList::new(
&mut self.theme_picklist,
Theme::all(),
Some(self.theme.clone()),
Message::ApplyTheme,
let expert_mode_checkbox = checkbox(
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
self.general.expert_mode,
Message::ExpertMode,
)
.style(style::PickList(self.theme.palette));
let uad_category_text = Text::new("UAD").size(25);
.style(style::CheckBox::SettingsEnabled);
let expert_mode_descr =
Text::new("Most of unsafe packages are known to bootloop the device if removed.")
.size(15)
.color(self.theme.palette.normal.surface);
text("Most of unsafe packages are known to bootloop the device if removed.")
.style(style::Text::Commentary)
.size(15);
let expert_mode_checkbox = Checkbox::new(
self.expert_mode,
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
Message::ExpertMode,
);
let disable_mode_descr = Text::new("Default mode on older phone (< Android 8.0) where uninstalled packages can't be restored.")
.size(15)
.color(self.theme.palette.normal.surface);
let disable_mode_checkbox = Checkbox::new(
self.disable_mode,
"Clear and disable packages instead of uninstalling them",
Message::DisableMode,
);
let multi_user_mode_descr =
Text::new("Disabling this setting will typically prevent affecting your work profile")
.size(15)
.color(self.theme.palette.normal.surface);
let multi_user_mode_checkbox = Checkbox::new(
self.multi_user_mode,
"Affect all the users of the phone (not only the selected user)",
Message::MultiUserMode,
);
let content = Column::new()
let general_ctn = container(column![expert_mode_checkbox, expert_mode_descr].spacing(10))
.padding(10)
.width(Length::Fill)
.spacing(10)
.push(general_category_text)
.push(Text::new("Theme"))
.push(theme_picklist)
.push(Space::new(Length::Fill, Length::Shrink))
.push(uad_category_text)
.push(expert_mode_checkbox)
.push(expert_mode_descr)
.push(Space::new(Length::Fill, Length::Shrink))
.push(disable_mode_checkbox)
.push(disable_mode_descr)
.push(Space::new(Length::Fill, Length::Shrink))
.push(multi_user_mode_checkbox)
.push(multi_user_mode_descr);
.height(Length::Shrink)
.style(style::Container::Frame);
Container::new(content)
let warning_ctn = container(
row![
text("The following settings only affect the currently selected device :")
.style(style::Text::Danger),
text(phone.model.clone()),
Space::new(Length::Fill, Length::Shrink),
text(phone.adb_id.clone()).style(style::Text::Commentary)
]
.spacing(7),
)
.padding(10)
.width(Length::Fill)
.style(style::Container::BorderedFrame);
let multi_user_mode_descr = row![
text("This will not affect the following protected work profile users: ")
.size(15)
.style(style::Text::Commentary),
text(
phone
.user_list
.iter()
.filter(|&u| u.protected)
.map(|u| u.id.to_string())
.collect::<Vec<String>>()
.join(", ")
)
.size(15)
.style(style::Text::Danger)
];
let multi_user_mode_checkbox = checkbox(
"Affect all the users of the device (not only the selected user)",
self.device.multi_user_mode,
Message::MultiUserMode,
)
.style(style::CheckBox::SettingsEnabled);
let disable_checkbox_style = if phone.android_sdk >= 23 {
style::CheckBox::SettingsEnabled
} else {
style::CheckBox::SettingsDisabled
};
let disable_mode_descr =
text("In some cases, it can be better to disable a package instead of uninstalling it")
.style(style::Text::Commentary)
.size(15);
let unavailable_btn = button(text("Unavailable").size(13))
.on_press(Message::UrlPressed(PathBuf::from(
"https://github.com/0x192/universal-android-debloater/wiki/FAQ#\
why-is-the-disable-mode-setting-not-available-for-my-device",
)))
.height(22)
.style(style::Button::Unavailable);
// Disabling package without root isn't really possible before Android Oreo (8.0)
// see https://github.com/0x192/universal-android-debloater/wiki/ADB-reference
let disable_mode_checkbox = checkbox(
"Clear and disable packages instead of uninstalling them",
self.device.disable_mode,
Message::DisableMode,
)
.style(disable_checkbox_style);
let disable_setting_row = if phone.android_sdk >= 23 {
row![
disable_mode_checkbox,
Space::new(Length::Fill, Length::Shrink),
]
.width(Length::Fill)
} else {
row![
disable_mode_checkbox,
Space::new(Length::Fill, Length::Shrink),
unavailable_btn,
]
.width(Length::Fill)
};
let device_specific_ctn = container(
column![
multi_user_mode_checkbox,
multi_user_mode_descr,
disable_setting_row,
disable_mode_descr,
]
.spacing(10),
)
.padding(10)
.width(Length::Fill)
.height(Length::Shrink)
.style(style::Container::Frame);
let backup_pick_list = pick_list(
self.device.backup.backups.clone(),
self.device.backup.selected.clone(),
Message::BackupSelected,
)
.padding(6);
let backup_btn = button(text("Backup").horizontal_alignment(alignment::Horizontal::Center))
.padding(5)
.on_press(Message::BackupDevice)
.style(style::Button::Primary)
.width(77);
let restore_btn = |enabled| {
if enabled {
button(text("Restore").horizontal_alignment(alignment::Horizontal::Center))
.padding(5)
.on_press(Message::RestoreDevice)
.width(77)
} else {
button(text("No backup").horizontal_alignment(alignment::Horizontal::Center))
.padding(5)
.width(77)
}
};
let locate_backup_btn = if self.device.backup.backups.is_empty() {
button("Open backup directory")
.padding(5)
.style(style::Button::Primary)
} else {
button("Open backup directory")
.on_press(Message::UrlPressed(BACKUP_DIR.join(phone.adb_id.clone())))
.padding(5)
.style(style::Button::Primary)
};
let backup_row = row![
backup_btn,
"Backup the current state of the phone",
Space::new(Length::Fill, Length::Shrink),
locate_backup_btn,
]
.spacing(10)
.align_items(Alignment::Center);
let restore_row = if self.device.backup.backups.is_empty() {
row![restore_btn(false), "Restore the state of the device",]
.spacing(10)
.align_items(Alignment::Center)
} else {
row![
restore_btn(true),
"Restore the state of the device",
Space::new(Length::Fill, Length::Shrink),
text(self.device.backup.backup_state.clone()).style(style::Text::Danger),
backup_pick_list,
]
.spacing(10)
.align_items(Alignment::Center)
};
let backup_restore_ctn = container(column![backup_row, restore_row].spacing(10))
.padding(10)
.width(Length::Fill)
.height(Length::Shrink)
.style(style::Container::Frame);
let no_device_ctn = || {
container(text("No device detected").style(style::Text::Danger))
.padding(10)
.width(Length::Fill)
.style(style::Container::BorderedFrame)
};
let content = if phone.adb_id.clone().is_empty() {
column![
text("Theme").size(25),
theme_ctn,
text("General").size(25),
general_ctn,
text("Current device").size(25),
no_device_ctn(),
text("Backup / Restore").size(25),
no_device_ctn(),
]
.width(Length::Fill)
.spacing(20)
} else {
column![
text("Theme").size(25),
theme_ctn,
text("General").size(25),
general_ctn,
text("Current device").size(25),
warning_ctn,
device_specific_ctn,
backup_restore_ctn,
]
.width(Length::Fill)
.spacing(20)
};
container(content)
.padding(10)
.width(Length::Fill)
.height(Length::Fill)
.style(style::Content(self.theme.palette))
.into()
}
}

View file

@ -1 +1,3 @@
pub mod modal;
pub mod navigation_menu;
pub mod package_row;

283
src/gui/widgets/modal.rs Normal file
View file

@ -0,0 +1,283 @@
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length,
Point, Rectangle, Shell, Size, Widget,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
#[allow(clippy::missing_const_for_fn)]
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Message) -> Self {
Self {
on_blur: Some(on_blur),
..self
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.base
.as_widget()
.operate(&mut state.children[0], layout, renderer, operation);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
#[allow(clippy::equatable_if_let)]
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
renderer,
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}

View file

@ -0,0 +1,113 @@
pub use crate::core::sync::Phone;
use crate::core::theme::Theme;
use crate::core::update::{SelfUpdateState, SelfUpdateStatus};
pub use crate::gui::views::about::Message as AboutMessage;
pub use crate::gui::views::list::{List as AppsView, LoadingState as ListLoadingState};
use crate::gui::{style, Message};
use iced::widget::{button, container, pick_list, row, text, Space, Text};
use iced::{alignment, Alignment, Element, Font, Length, Renderer};
pub const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../../resources/assets/icons.ttf"),
};
pub fn nav_menu<'a>(
device_list: &'a Vec<Phone>,
selected_device: Option<Phone>,
apps_view: &AppsView,
self_update_state: &SelfUpdateState,
) -> Element<'a, Message, Renderer<Theme>> {
let apps_refresh_btn = button(
Text::new("\u{E900}")
.font(ICONS)
.width(17)
.horizontal_alignment(alignment::Horizontal::Center)
.size(17),
)
.on_press(Message::RefreshButtonPressed)
.padding(5)
.style(style::Button::Refresh);
let reboot_btn = button("Reboot")
.on_press(Message::RebootButtonPressed)
.padding(5)
.style(style::Button::Refresh);
#[allow(clippy::option_if_let_else)]
let uad_version_text = if let Some(r) = &self_update_state.latest_release {
if self_update_state.status == SelfUpdateStatus::Updating {
Text::new("Updating please wait...")
} else {
Text::new(format!(
"New UAD version available {} -> {}",
env!("CARGO_PKG_VERSION"),
r.tag_name
))
}
} else {
Text::new(env!("CARGO_PKG_VERSION"))
};
let apps_btn = if self_update_state.latest_release.is_some() {
button("Update")
.on_press(Message::AboutAction(AboutMessage::DoSelfUpdate))
.padding(5)
.style(style::Button::SelfUpdate)
} else {
button("Apps")
.on_press(Message::AppsPress)
.padding(5)
.style(style::Button::Primary)
};
let about_btn = button("About")
.on_press(Message::AboutPressed)
.padding(5)
.style(style::Button::Primary);
let settings_btn = button("Settings")
.on_press(Message::SettingsPressed)
.padding(5)
.style(style::Button::Primary);
let device_list_text = match apps_view.loading_state {
ListLoadingState::FindingPhones(_) => text("finding connected phone..."),
_ => text("no devices/emulators found"),
};
let row = match selected_device {
Some(phone) => row![
apps_refresh_btn,
reboot_btn,
pick_list(device_list, Some(phone), Message::DeviceSelected,),
Space::new(Length::Fill, Length::Shrink),
uad_version_text,
apps_btn,
about_btn,
settings_btn,
]
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10),
None => row![
reboot_btn,
apps_refresh_btn,
device_list_text,
Space::new(Length::Fill, Length::Shrink),
uad_version_text,
apps_btn,
about_btn,
settings_btn,
]
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10),
};
container(row)
.width(Length::Fill)
.padding(10)
.style(style::Container::Frame)
.into()
}

View file

@ -1,12 +1,11 @@
use crate::core::sync::Phone;
use crate::core::theme::Theme;
use crate::core::uad_lists::{PackageState, Removal, UadList};
use crate::gui::style;
use crate::gui::views::settings::Settings;
use iced::{
alignment, button, Alignment, Button, Checkbox, Command, Element, Length, Row, Space, Text,
};
use iced::widget::{button, checkbox, row, text, Space};
use iced::{alignment, Alignment, Command, Element, Length, Renderer};
#[derive(Clone, Debug)]
pub struct PackageRow {
@ -15,9 +14,8 @@ pub struct PackageRow {
pub description: String,
pub uad_list: UadList,
pub removal: Removal,
package_btn_state: button::State,
action_btn_state: button::State,
pub selected: bool,
pub current: bool,
}
#[derive(Clone, Debug)]
@ -35,6 +33,7 @@ impl PackageRow {
uad_list: UadList,
removal: Removal,
selected: bool,
current: bool,
) -> Self {
Self {
name: name.to_string(),
@ -42,19 +41,18 @@ impl PackageRow {
description: description.to_string(),
uad_list,
removal,
package_btn_state: button::State::default(),
action_btn_state: button::State::default(),
selected,
current,
}
}
pub fn update(&mut self, _message: Message) -> Command<Message> {
pub fn update(&mut self, _message: &Message) -> Command<Message> {
Command::none()
}
pub fn view(&mut self, settings: &Settings, phone: &Phone) -> Element<Message> {
//let trash_svg = format!("{}/ressources/assets/trash.svg", env!("CARGO_MANIFEST_DIR"));
//let restore_svg = format!("{}/ressources/assets/rotate.svg", env!("CARGO_MANIFEST_DIR"));
pub fn view(&self, settings: &Settings, _phone: &Phone) -> Element<Message, Renderer<Theme>> {
//let trash_svg = format!("{}/resources/assets/trash.svg", env!("CARGO_MANIFEST_DIR"));
//let restore_svg = format!("{}/resources/assets/rotate.svg", env!("CARGO_MANIFEST_DIR"));
let button_style;
let action_text;
let action_btn;
@ -62,66 +60,72 @@ impl PackageRow {
match self.state {
PackageState::Enabled => {
action_text = if settings.disable_mode {
action_text = if settings.device.disable_mode {
"Disable"
} else {
"Uninstall"
};
button_style = style::PackageButton::Uninstall(settings.theme.palette);
button_style = style::Button::UninstallPackage;
}
PackageState::Disabled => {
action_text = "Enable";
button_style = style::PackageButton::Restore(settings.theme.palette);
button_style = style::Button::RestorePackage;
}
PackageState::Uninstalled => {
action_text = "Restore";
button_style = style::PackageButton::Restore(settings.theme.palette);
button_style = style::Button::RestorePackage;
}
PackageState::All => {
action_text = "Error";
button_style = style::PackageButton::Restore(settings.theme.palette);
warn!("Incredible! Something impossible happenned!");
button_style = style::Button::RestorePackage;
warn!("Incredible! Something impossible happened!");
}
}
if (self.state != PackageState::Uninstalled && settings.expert_mode)
|| (self.removal != Removal::Unsafe && phone.android_sdk >= 26)
// Disable any removal action for unsafe packages if expert_mode is disabled
if self.removal != Removal::Unsafe
|| self.state != PackageState::Enabled
|| settings.general.expert_mode
{
selection_checkbox = Checkbox::new(self.selected, "", Message::ToggleSelection)
.style(style::SelectionCheckBox::Enabled(settings.theme.palette));
selection_checkbox = checkbox("", self.selected, Message::ToggleSelection)
.style(style::CheckBox::PackageEnabled);
action_btn = Button::new(
&mut self.action_btn_state,
Text::new(action_text).horizontal_alignment(alignment::Horizontal::Center),
action_btn = button(
text(action_text)
.horizontal_alignment(alignment::Horizontal::Center)
.width(100),
)
.on_press(Message::ActionPressed);
} else {
selection_checkbox = Checkbox::new(self.selected, "", Message::ToggleSelection)
.style(style::SelectionCheckBox::Disabled(settings.theme.palette));
selection_checkbox = checkbox("", self.selected, Message::ToggleSelection)
.style(style::CheckBox::PackageDisabled);
action_btn = Button::new(
&mut self.action_btn_state,
Text::new(action_text).horizontal_alignment(alignment::Horizontal::Center),
action_btn = button(
text(action_text)
.horizontal_alignment(alignment::Horizontal::Center)
.width(100),
);
}
Row::new()
.push(
Button::new(
&mut self.package_btn_state,
Row::new()
.align_items(Alignment::Center)
.push(selection_checkbox)
.push(Text::new(&self.name).width(Length::FillPortion(8)))
.push(action_btn.width(Length::FillPortion(1)).style(button_style)),
)
.padding(8)
.style(style::PackageRow(settings.theme.palette))
.width(Length::Fill)
.on_press(Message::PackagePressed),
row![
button(
row![
selection_checkbox,
text(&self.name).width(Length::FillPortion(8)),
action_btn.style(button_style)
]
.align_items(Alignment::Center)
)
.push(Space::with_width(Length::Units(15)))
.align_items(Alignment::Center)
.into()
.padding(8)
.style(if self.current {
style::Button::SelectedPackage
} else {
style::Button::NormalPackage
})
.width(Length::Fill)
.on_press(Message::PackagePressed),
Space::with_width(15)
]
.align_items(Alignment::Center)
.into()
}
}

View file

@ -1,21 +1,29 @@
#![windows_subsystem = "windows"]
#![feature(drain_filter)]
#[macro_use]
extern crate log;
use crate::core::utils::setup_uad_dir;
use fern::{
colors::{Color, ColoredLevelConfig},
FormatCallback,
};
use log::Record;
use static_init::dynamic;
use std::path::PathBuf;
use std::{fmt::Arguments, fs::OpenOptions};
mod core;
mod gui;
fn main() {
#[dynamic]
static CONFIG_DIR: PathBuf = setup_uad_dir(dirs::config_dir());
#[dynamic]
static CACHE_DIR: PathBuf = setup_uad_dir(dirs::cache_dir());
fn main() -> iced::Result {
setup_logger().expect("setup logging");
gui::UadGui::start();
gui::UadGui::start()
}
pub fn setup_logger() -> Result<(), fern::InitError> {
@ -34,7 +42,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
record.file().unwrap_or("?"),
record.line().map(|l| l.to_string()).unwrap_or_default(),
message
))
));
}
};
@ -42,9 +50,9 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
let log_file = OpenOptions::new()
.write(true)
.create(true)
.append(false)
.truncate(true)
.open("uad.log")?;
.append(true)
.truncate(false)
.open(CACHE_DIR.join(format!("UAD_{}.log", chrono::Local::now().format("%Y%m%d"))))?;
let file_dispatcher = fern::Dispatch::new()
.format(make_formatter(false))