├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── kotlin
│ │ └── com
│ │ └── apkupdater
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ ├── androidx
│ │ │ └── compose
│ │ │ │ └── material3
│ │ │ │ └── pullrefresh
│ │ │ │ ├── PullRefresh.kt
│ │ │ │ ├── PullRefreshIndicator.kt
│ │ │ │ ├── PullRefreshIndicatorTransform.kt
│ │ │ │ └── PullRefreshState.kt
│ │ └── com
│ │ │ └── apkupdater
│ │ │ ├── application
│ │ │ └── App.kt
│ │ │ ├── data
│ │ │ ├── apkmirror
│ │ │ │ ├── AppExistsRequest.kt
│ │ │ │ ├── AppExistsResponse.kt
│ │ │ │ ├── AppExistsResponseApk.kt
│ │ │ │ ├── AppExistsResponseApp.kt
│ │ │ │ ├── AppExistsResponseData.kt
│ │ │ │ ├── AppExistsResponseDeveloper.kt
│ │ │ │ ├── AppExistsResponseHeaders.kt
│ │ │ │ └── AppExistsResponseRelease.kt
│ │ │ ├── apkpure
│ │ │ │ ├── AppInfoForUpdate.kt
│ │ │ │ ├── AppUpdateResponse.kt
│ │ │ │ ├── AppUpdateResponseAsset.kt
│ │ │ │ ├── AppUpdateResponseIcon.kt
│ │ │ │ ├── AppUpdateResponseIconData.kt
│ │ │ │ ├── DeviceHeader.kt
│ │ │ │ ├── DeviceInfo.kt
│ │ │ │ ├── GetAppUpdate.kt
│ │ │ │ ├── GetAppUpdateResponse.kt
│ │ │ │ ├── SearchResponse.kt
│ │ │ │ ├── SearchResponseApp.kt
│ │ │ │ ├── SearchResponseData.kt
│ │ │ │ └── SearchResponseItem.kt
│ │ │ ├── aptoide
│ │ │ │ ├── ApksData.kt
│ │ │ │ ├── App.kt
│ │ │ │ ├── DataList.kt
│ │ │ │ ├── File.kt
│ │ │ │ ├── ListAppUpdatesResponse.kt
│ │ │ │ ├── ListAppsUpdatesRequest.kt
│ │ │ │ ├── ListSearchAppsRequest.kt
│ │ │ │ ├── ListSearchAppsResponse.kt
│ │ │ │ └── Store.kt
│ │ │ ├── fdroid
│ │ │ │ ├── FdroidApp.kt
│ │ │ │ ├── FdroidData.kt
│ │ │ │ ├── FdroidLocalized.kt
│ │ │ │ ├── FdroidPackage.kt
│ │ │ │ └── FdroidUpdate.kt
│ │ │ ├── github
│ │ │ │ ├── GitHubApp.kt
│ │ │ │ ├── GitHubAuthor.kt
│ │ │ │ ├── GitHubRelease.kt
│ │ │ │ └── GitHubReleaseAsset.kt
│ │ │ ├── gitlab
│ │ │ │ ├── GitLabApp.kt
│ │ │ │ ├── GitLabAsset.kt
│ │ │ │ ├── GitLabAssets.kt
│ │ │ │ ├── GitLabAuthor.kt
│ │ │ │ ├── GitLabLink.kt
│ │ │ │ └── GitLabRelease.kt
│ │ │ ├── snack
│ │ │ │ └── TextSnack.kt
│ │ │ └── ui
│ │ │ │ ├── AppInstallProgress.kt
│ │ │ │ ├── AppInstallStatus.kt
│ │ │ │ ├── AppInstalled.kt
│ │ │ │ ├── AppUpdate.kt
│ │ │ │ ├── AppsUiState.kt
│ │ │ │ ├── Link.kt
│ │ │ │ ├── Screen.kt
│ │ │ │ ├── SearchUiState.kt
│ │ │ │ ├── SettingsUiState.kt
│ │ │ │ ├── Source.kt
│ │ │ │ └── UpdatesUiState.kt
│ │ │ ├── di
│ │ │ └── MainModule.kt
│ │ │ ├── prefs
│ │ │ └── Prefs.kt
│ │ │ ├── repository
│ │ │ ├── ApkMirrorRepository.kt
│ │ │ ├── ApkPureRepository.kt
│ │ │ ├── AppsRepository.kt
│ │ │ ├── AptoideRepository.kt
│ │ │ ├── FdroidRepository.kt
│ │ │ ├── GitHubRepository.kt
│ │ │ ├── GitLabRepository.kt
│ │ │ ├── PlayRepository.kt
│ │ │ ├── SearchRepository.kt
│ │ │ └── UpdatesRepository.kt
│ │ │ ├── service
│ │ │ ├── ApkMirrorService.kt
│ │ │ ├── ApkPureService.kt
│ │ │ ├── AptoideService.kt
│ │ │ ├── FdroidService.kt
│ │ │ ├── GitHubService.kt
│ │ │ └── GitLabService.kt
│ │ │ ├── transform
│ │ │ └── Transforms.kt
│ │ │ ├── ui
│ │ │ ├── activity
│ │ │ │ └── MainActivity.kt
│ │ │ ├── component
│ │ │ │ ├── Grid.kt
│ │ │ │ ├── Icons.kt
│ │ │ │ ├── Image.kt
│ │ │ │ ├── Modifier.kt
│ │ │ │ ├── Settings.kt
│ │ │ │ ├── Text.kt
│ │ │ │ ├── TvComponents.kt
│ │ │ │ └── UiComponents.kt
│ │ │ ├── screen
│ │ │ │ ├── AppsScreen.kt
│ │ │ │ ├── MainScreen.kt
│ │ │ │ ├── SearchScreen.kt
│ │ │ │ ├── SettingsScreen.kt
│ │ │ │ └── UpdatesScreen.kt
│ │ │ └── theme
│ │ │ │ └── Theme.kt
│ │ │ ├── util
│ │ │ ├── Badger.kt
│ │ │ ├── Clipboard.kt
│ │ │ ├── Downloader.kt
│ │ │ ├── Extensions.kt
│ │ │ ├── InstallLog.kt
│ │ │ ├── SessionInstaller.kt
│ │ │ ├── SnackBar.kt
│ │ │ ├── Stringer.kt
│ │ │ ├── Themer.kt
│ │ │ ├── UpdatesNotification.kt
│ │ │ └── play
│ │ │ │ ├── EglExtensionProvider.kt
│ │ │ │ ├── IProxyHttpClient.kt
│ │ │ │ ├── NativeDeviceInfoProvider.kt
│ │ │ │ ├── PlayHttpClient.kt
│ │ │ │ └── ProxyInfo.kt
│ │ │ ├── viewmodel
│ │ │ ├── AppsViewModel.kt
│ │ │ ├── InstallViewModel.kt
│ │ │ ├── MainViewModel.kt
│ │ │ ├── SearchViewModel.kt
│ │ │ ├── SettingsViewModel.kt
│ │ │ └── UpdatesViewModel.kt
│ │ │ └── worker
│ │ │ └── UpdatesWorker.kt
│ └── res
│ │ ├── drawable
│ │ ├── banner.png
│ │ ├── ic_alarm.xml
│ │ ├── ic_alpha.xml
│ │ ├── ic_androidtv.xml
│ │ ├── ic_animation.xml
│ │ ├── ic_apkmirror.png
│ │ ├── ic_apkpure.png
│ │ ├── ic_appstore.xml
│ │ ├── ic_appstore_off.xml
│ │ ├── ic_aptoide.png
│ │ ├── ic_beta.xml
│ │ ├── ic_copy.xml
│ │ ├── ic_disabled.xml
│ │ ├── ic_disabled_off.xml
│ │ ├── ic_empty.xml
│ │ ├── ic_fdroid.xml
│ │ ├── ic_frequency.xml
│ │ ├── ic_github.xml
│ │ ├── ic_gitlab.xml
│ │ ├── ic_hour.xml
│ │ ├── ic_info.xml
│ │ ├── ic_install.xml
│ │ ├── ic_izzy.png
│ │ ├── ic_landscape.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_play.xml
│ │ ├── ic_portrait.xml
│ │ ├── ic_pre_release.xml
│ │ ├── ic_refresh.xml
│ │ ├── ic_root.xml
│ │ ├── ic_safe.xml
│ │ ├── ic_system.xml
│ │ ├── ic_system_off.xml
│ │ ├── ic_theme.xml
│ │ ├── ic_visible.xml
│ │ └── ic_visible_off.xml
│ │ ├── mipmap-anydpi-v26
│ │ └── ic_launcher.xml
│ │ ├── mipmap-anydpi-v33
│ │ └── ic_launcher.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-alb
│ │ └── strings.xml
│ │ ├── values-ar
│ │ └── strings.xml
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-he
│ │ └── strings.xml
│ │ ├── values-hu
│ │ └── strings.xml
│ │ ├── values-in
│ │ └── strings.xml
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-jp
│ │ └── strings.xml
│ │ ├── values-ko
│ │ └── strings.xml
│ │ ├── values-ms
│ │ └── strings.xml
│ │ ├── values-my-rMM
│ │ └── strings.xml
│ │ ├── values-nl
│ │ └── strings.xml
│ │ ├── values-pt
│ │ └── strings.xml
│ │ ├── values-ro
│ │ └── strings.xml
│ │ ├── values-ru
│ │ └── strings.xml
│ │ ├── values-ta
│ │ └── strings.xml
│ │ ├── values-tk
│ │ └── strings.xml
│ │ ├── values-tr
│ │ └── strings.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ └── strings.xml
│ │ ├── values
│ │ └── strings.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── kotlin
│ └── com
│ └── apkupdater
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ ├── de
│ └── short_description.txt
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 01.png
│ │ ├── 02.png
│ │ ├── 03.png
│ │ ├── 04.png
│ │ ├── 05.png
│ │ └── 06.png
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Android Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 |
11 | - name: Setup Java 17
12 | uses: actions/setup-java@v4
13 | with:
14 | distribution: 'zulu'
15 | java-version: '17'
16 |
17 | - name: Decode Keystore
18 | env:
19 | KEYSTORE: ${{ secrets.KEYSTORE }}
20 | run: echo $KEYSTORE | base64 --decode > ./app/ci.ks
21 |
22 | - name: Decode Properties
23 | env:
24 | PROPERTIES: ${{ secrets.PROPERTIES }}
25 | run: echo $PROPERTIES | base64 --decode > local.properties
26 |
27 | - name: Build with Gradle
28 | env:
29 | BUILD_TAG: ".ci"
30 | BUILD_NUMBER: ${{ github.run_number }}
31 | run: ./gradlew build
32 |
33 | - name: Get current branch name
34 | run: echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
35 |
36 | - name: Create Release
37 | uses: softprops/action-gh-release@v2
38 | if: github.ref == 'refs/heads/3.x'
39 | with:
40 | tag_name: 0.0.${{ github.run_number }}-ci
41 | name: CI-Release-${{ env.BRANCH }}-${{ github.run_number }}
42 | files: ./app/build/outputs/apk/release/com.apkupdater.ci-release.apk
43 | body: ${{ github.event.head_commit.message }}
44 | prerelease: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | .DS_Store
4 | /build
5 | /captures
6 | .externalNativeBuild
7 | .cxx
8 | /.idea/
9 | /app/build/
10 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keepattributes Signature
2 |
3 | # Gson
4 | -keep class com.google.gson.reflect.TypeToken { *; }
5 | -keep class * extends com.google.gson.reflect.TypeToken
6 |
7 | # OkHttp
8 | -keep,allowobfuscation,allowshrinking class okhttp3.RequestBody
9 | -keep,allowobfuscation,allowshrinking class okhttp3.ResponseBody
10 |
11 | #Retrofit
12 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
13 | -keep,allowobfuscation,allowshrinking interface retrofit2.Call
14 | -keep,allowobfuscation,allowshrinking class retrofit2.Response
15 | -if interface * { @retrofit2.http.* public *** *(...); }
16 | -keep,allowoptimization,allowshrinking,allowobfuscation class <3>
17 |
18 | # Models
19 | -keep class com.apkupdater.data.** { *; }
20 |
--------------------------------------------------------------------------------
/app/src/androidTest/kotlin/com/apkupdater/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.apkupdater", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/androidx/compose/material3/pullrefresh/PullRefreshIndicatorTransform.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package androidx.compose.material3.pullrefresh
18 |
19 | import androidx.compose.animation.core.LinearOutSlowInEasing
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.draw.drawWithContent
22 | import androidx.compose.ui.graphics.drawscope.clipRect
23 | import androidx.compose.ui.graphics.graphicsLayer
24 | import androidx.compose.ui.platform.debugInspectorInfo
25 | import androidx.compose.ui.platform.inspectable
26 |
27 | /**
28 | * A modifier for translating the position and scaling the size of a pull-to-refresh indicator
29 | * based on the given [PullRefreshState].
30 | *
31 | * @sample androidx.compose.material.samples.PullRefreshIndicatorTransformSample
32 | *
33 | * @param state The [PullRefreshState] which determines the position of the indicator.
34 | * @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
35 | */
36 | // TODO: Consider whether the state parameter should be replaced with lambdas.
37 | fun Modifier.pullRefreshIndicatorTransform(
38 | state: PullRefreshState,
39 | scale: Boolean = false,
40 | ) = inspectable(inspectorInfo = debugInspectorInfo {
41 | name = "pullRefreshIndicatorTransform"
42 | properties["state"] = state
43 | properties["scale"] = scale
44 | }) {
45 | Modifier
46 | // Essentially we only want to clip the at the top, so the indicator will not appear when
47 | // the position is 0. It is preferable to clip the indicator as opposed to the layout that
48 | // contains the indicator, as this would also end up clipping shadows drawn by items in a
49 | // list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE
50 | // for the other dimensions to allow for more room for elevation / arbitrary indicators - we
51 | // only ever really want to clip at the top edge.
52 | .drawWithContent {
53 | clipRect(
54 | top = 0f,
55 | left = -Float.MAX_VALUE,
56 | right = Float.MAX_VALUE,
57 | bottom = Float.MAX_VALUE
58 | ) {
59 | this@drawWithContent.drawContent()
60 | }
61 | }
62 | .graphicsLayer {
63 | translationY = state.position - size.height
64 |
65 | if (scale && !state.refreshing) {
66 | val scaleFraction = LinearOutSlowInEasing
67 | .transform(state.position / state.threshold)
68 | .coerceIn(0f, 1f)
69 | scaleX = scaleFraction
70 | scaleY = scaleFraction
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/application/App.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.application
2 |
3 | import android.app.Application
4 | import coil.ImageLoader
5 | import coil.ImageLoaderFactory
6 | import com.apkupdater.di.mainModule
7 | import okhttp3.OkHttpClient
8 | import org.koin.android.ext.koin.androidContext
9 | import org.koin.android.ext.koin.androidLogger
10 | import org.koin.core.component.KoinComponent
11 | import org.koin.core.component.get
12 | import org.koin.core.context.startKoin
13 |
14 | class App : Application(), ImageLoaderFactory, KoinComponent {
15 |
16 | override fun onCreate() {
17 | super.onCreate()
18 |
19 | startKoin {
20 | androidLogger()
21 | androidContext(this@App)
22 | modules(mainModule)
23 | }
24 | }
25 |
26 | override fun newImageLoader() = ImageLoader
27 | .Builder(this)
28 | .okHttpClient(get())
29 | //.logger(DebugLogger())
30 | .build()
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsRequest.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | data class AppExistsRequest(
4 | val pnames: List,
5 | val exclude: List = listOf("alpha", "beta")
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | data class AppExistsResponse(
4 | val data: List = emptyList(),
5 | val headers: AppExistsResponseHeaders? = null,
6 | val status: Int? = null
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseApk.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | import com.apkupdater.data.ui.ApkMirrorSource
4 | import com.apkupdater.data.ui.AppInstalled
5 | import com.apkupdater.data.ui.AppUpdate
6 | import com.apkupdater.data.ui.Link
7 | import com.google.gson.annotations.SerializedName
8 |
9 | data class AppExistsResponseApk(
10 | @SerializedName("version_code") val versionCode: Long = 0,
11 | val link: String = "",
12 | @SerializedName("publish_date") val publishDate: String? = null,
13 | val arches: List = emptyList(),
14 | val dpis: List? = null,
15 | val minapi: String = "0",
16 | val description: String? = null,
17 | val capabilities: List? = null,
18 | @SerializedName("signatures-sha1")
19 | val signaturesSha1: List? = emptyList(),
20 | @SerializedName("signatures-sha256")
21 | val signaturesSha256: List? = emptyList()
22 | )
23 |
24 | fun AppExistsResponseApk.toAppUpdate(app: AppInstalled, release: AppExistsResponseRelease) = AppUpdate(
25 | app.name,
26 | app.packageName,
27 | release.version,
28 | app.version,
29 | versionCode,
30 | app.versionCode,
31 | ApkMirrorSource,
32 | app.iconUri,
33 | Link.Url("https://www.apkmirror.com$link"),
34 | release.whatsNew.orEmpty()
35 | )
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseApp.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | data class AppExistsResponseApp(
4 | val name: String = "",
5 | val description: String? = null,
6 | val link: String? = null
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseData.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | data class AppExistsResponseData(
4 | val pname: String = "",
5 | val exists: Boolean? = null,
6 | val developer: AppExistsResponseDeveloper? = null,
7 | val app: AppExistsResponseApp = AppExistsResponseApp(),
8 | val release: AppExistsResponseRelease = AppExistsResponseRelease(),
9 | val apks: List = emptyList()
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseDeveloper.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | data class AppExistsResponseDeveloper(
4 | val name: String? = null,
5 | val link: String? = null
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseHeaders.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class AppExistsResponseHeaders(@SerializedName("Allow") val allow: String? = null)
6 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkmirror/AppExistsResponseRelease.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkmirror
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class AppExistsResponseRelease(
6 | val version: String = "",
7 | @SerializedName("publish_date") val publishDate: String? = null,
8 | @SerializedName("whats_new") val whatsNew: String? = null,
9 | val link: String? = null
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/AppInfoForUpdate.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class AppInfoForUpdate(
5 | val package_name: String,
6 | val version_code: Long,
7 | val is_system: Boolean = false,
8 | val version_id: String = "",
9 | val cached_size: Int = -1
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/AppUpdateResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 | import android.net.Uri
4 | import com.apkupdater.data.ui.ApkPureSource
5 | import com.apkupdater.data.ui.AppInstalled
6 | import com.apkupdater.data.ui.AppUpdate
7 | import com.apkupdater.data.ui.Link
8 |
9 |
10 | data class AppUpdateResponse(
11 | val package_name: String,
12 | val version_code: Long,
13 | val version_name: String,
14 | val sign: List,
15 | val whatsnew: String,
16 | val description_short: String,
17 | val label: String,
18 | val asset: AppUpdateResponseAsset,
19 | val icon: AppUpdateResponseIcon
20 | )
21 |
22 | fun AppUpdateResponse.toAppUpdate(
23 | app: AppInstalled?
24 | ) = AppUpdate(
25 | label,
26 | package_name,
27 | version_name,
28 | app?.version.orEmpty(),
29 | version_code,
30 | app?.versionCode ?: 0L,
31 | ApkPureSource,
32 | if (app == null) Uri.parse(icon.thumbnail.url) else Uri.EMPTY,
33 | if (asset.url.contains("/XAPK")) Link.Xapk(asset.url.replace("http://", "https://")) else Link.Url(asset.url.replace("http://", "https://")),
34 | if (app == null) description_short else whatsnew
35 | )
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/AppUpdateResponseAsset.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class AppUpdateResponseAsset(
5 | val type: String,
6 | val url: String
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/AppUpdateResponseIcon.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class AppUpdateResponseIcon(
5 | val original: AppUpdateResponseIconData,
6 | val thumbnail: AppUpdateResponseIconData
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/AppUpdateResponseIconData.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class AppUpdateResponseIconData(
5 | val height: String,
6 | val width: String,
7 | val url: String
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/DeviceHeader.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class DeviceHeader(
5 | val device_info: DeviceInfo = DeviceInfo()
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/DeviceInfo.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 | import android.content.res.Resources
4 | import android.os.Build
5 | import okhttp3.internal.toHexString
6 | import kotlin.random.Random
7 |
8 |
9 | data class DeviceInfo(
10 | val abis: List = Build.SUPPORTED_ABIS.toList(),
11 | val android_id: String = Random.nextLong().toHexString(),
12 | val os_ver: String = Build.VERSION.SDK_INT.toString(),
13 | val os_ver_name: String = Build.VERSION.RELEASE,
14 | val platform: Int = 1, // TODO: Figure out what is this
15 | val screen_height: Int = Resources.getSystem().displayMetrics.heightPixels,
16 | val screen_width: Int = Resources.getSystem().displayMetrics.widthPixels
17 | )
18 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/GetAppUpdate.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 | import okhttp3.internal.toHexString
4 | import kotlin.random.Random
5 |
6 |
7 | data class GetAppUpdate(
8 | val app_info_for_update: List = emptyList(),
9 | val android_id: String = Random.nextLong().toHexString(),
10 | val application_id: String = "com.apkpure.aegon",
11 | val cached_size: Long = -1
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/GetAppUpdateResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class GetAppUpdateResponse(
5 | val retcode: Int,
6 | val app_update_response: List
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/SearchResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class SearchResponse(
5 | val ad_priority: Int,
6 | val data: SearchResponseData
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/SearchResponseApp.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class SearchResponseApp(
5 | val ad: Boolean,
6 | val app_info: AppUpdateResponse
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/SearchResponseData.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class SearchResponseData(
5 | val data: List
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/apkpure/SearchResponseItem.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.apkpure
2 |
3 |
4 | data class SearchResponseItem(
5 | val data: List
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/ApksData.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class ApksData(
6 | @SerializedName("package") val packageName: String = "",
7 | val vercode: String = "0",
8 | val signature: String?,
9 | val isEnabled: Boolean = true
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/App.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 | import android.net.Uri
4 | import androidx.core.net.toUri
5 | import com.apkupdater.data.ui.AppInstalled
6 | import com.apkupdater.data.ui.AppUpdate
7 | import com.apkupdater.data.ui.AptoideSource
8 | import com.apkupdater.data.ui.Link
9 | import com.google.gson.annotations.SerializedName
10 |
11 | data class App(
12 | val name: String = "",
13 | @SerializedName("package") val packageName:String = "",
14 | val icon: String? = "",
15 | val file: File,
16 | val store: Store
17 | )
18 |
19 | fun App.toAppUpdate(app: AppInstalled?) = AppUpdate(
20 | name = name,
21 | packageName = packageName,
22 | version = file.vername,
23 | oldVersion = app?.version ?: "?",
24 | versionCode = file.vercode.toLong(),
25 | oldVersionCode = app?.versionCode ?: 0L,
26 | source = AptoideSource,
27 | iconUri = icon?.toUri() ?: Uri.EMPTY,
28 | link = Link.Url(file.path)
29 | )
30 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/DataList.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 | data class DataList(val list: List = emptyList())
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/File.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 | data class File(
4 | val vername: String = "",
5 | val vercode: String = "0",
6 | val path: String = ""
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/ListAppUpdatesResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 |
4 | data class ListAppUpdatesResponse(
5 | val list: List,
6 | val info: Any,
7 | val errors: Any
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/ListAppsUpdatesRequest.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 |
4 | data class ListAppsUpdatesRequest(
5 | val apks_data: List,
6 | val q: String,
7 | val not_apk_tags: String = "alpha,beta",
8 | val store_ids: List? = listOf(15L, 711454L)
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/ListSearchAppsRequest.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 |
4 | data class ListSearchAppsRequest(
5 | val query: String = "",
6 | val limit: String = "10",
7 | val q: String,
8 | val not_apk_tags: String = "alpha,beta",
9 | val store_ids: List? = listOf(15L, 711454L)
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/ListSearchAppsResponse.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 |
4 | data class ListSearchAppsResponse(
5 | val datalist: DataList,
6 | val info: Any,
7 | val errors: Any
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/aptoide/Store.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.aptoide
2 |
3 | data class Store(val name: String = "")
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/fdroid/FdroidApp.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.fdroid
2 |
3 | data class FdroidApp(
4 | //val description: String = "",
5 | val packageName: String = "",
6 | val name: String = "",
7 | val icon: String = "",
8 | val suggestedVersionCode: String = "",
9 | val suggestedVersionName: String = "",
10 | val added: Long = 0L,
11 | val lastUpdated: Long = 0L,
12 | val allowedAPKSigningKeys: List = emptyList(),
13 | val localized: Map = emptyMap()
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/fdroid/FdroidData.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.fdroid
2 |
3 | data class FdroidData(
4 | val packages: Map> = emptyMap(),
5 | val apps: List = emptyList()
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/fdroid/FdroidLocalized.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.fdroid
2 |
3 |
4 | data class FdroidLocalized(
5 | val name: String = "",
6 | val summary: String = "",
7 | val whatsNew: String = ""
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/fdroid/FdroidPackage.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.fdroid
2 |
3 | data class FdroidPackage(
4 | val apkName: String = "",
5 | val versionCode: Long = 0,
6 | val versionName: String = "",
7 | val minSdkVersion: Int = 0,
8 | val nativecode: List = emptyList(),
9 | val hash: String = "",
10 | val hashType: String = ""
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/fdroid/FdroidUpdate.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.fdroid
2 |
3 | import androidx.core.net.toUri
4 | import com.apkupdater.data.ui.AppInstalled
5 | import com.apkupdater.data.ui.AppUpdate
6 | import com.apkupdater.data.ui.Link
7 | import com.apkupdater.data.ui.Source
8 |
9 | data class FdroidUpdate(
10 | val apk: FdroidPackage,
11 | val app: FdroidApp
12 | )
13 |
14 | fun FdroidUpdate.toAppUpdate(current: AppInstalled?, source: Source, url: String) = AppUpdate(
15 | app.localized["en-US"]?.name ?: app.name,
16 | app.packageName,
17 | apk.versionName,
18 | current?.version ?: "?",
19 | apk.versionCode,
20 | current?.versionCode ?: 0L,
21 | source,
22 | if(app.icon.isEmpty())
23 | "https://f-droid.org/assets/ic_repo_app_default.png".toUri()
24 | else
25 | "${url}icons-640/${app.icon}".toUri(),
26 | Link.Url("$url${apk.apkName}"),
27 | if (current != null) app.localized["en-US"]?.whatsNew.orEmpty() else app.localized["en-US"]?.summary.orEmpty()
28 | )
29 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/github/GitHubAuthor.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.github
2 |
3 | data class GitHubAuthor(val avatar_url: String)
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/github/GitHubRelease.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.github
2 |
3 |
4 | data class GitHubRelease(
5 | val name: String,
6 | val prerelease: Boolean,
7 | val assets: List,
8 | val tag_name: String,
9 | val author: GitHubAuthor,
10 | val body: String = ""
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/github/GitHubReleaseAsset.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.github
2 |
3 | data class GitHubReleaseAsset(
4 | val size: Long,
5 | val browser_download_url: String
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabApp.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 | data class GitLabApp(
4 | val packageName: String,
5 | val user: String,
6 | val repo: String
7 | )
8 |
9 | val GitLabApps = listOf(
10 | GitLabApp("com.aurora.store", "AuroraOSS", "AuroraStore")
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabAsset.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 |
4 | data class GitLabAsset(val format: String, val url: String)
5 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabAssets.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 |
4 | data class GitLabAssets(
5 | val sources: List,
6 | val links: List
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabAuthor.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 |
4 | data class GitLabAuthor(val avatar_url: String)
5 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabLink.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 |
4 | data class GitLabLink(val url: String)
5 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/gitlab/GitLabRelease.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.gitlab
2 |
3 |
4 | data class GitLabRelease(
5 | val tag_name: String,
6 | val description: String,
7 | val assets: GitLabAssets,
8 | val author: GitLabAuthor
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/snack/TextSnack.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.snack
2 |
3 | import androidx.compose.material3.SnackbarDuration
4 | import androidx.compose.material3.SnackbarVisuals
5 |
6 |
7 | class TextSnack(
8 | override val message: String,
9 | override val actionLabel: String? = null,
10 | override val duration: SnackbarDuration = SnackbarDuration.Short,
11 | override val withDismissAction: Boolean = true
12 | ): SnackbarVisuals
13 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/AppInstallProgress.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | data class AppInstallProgress(val id: Int, val progress: Long? = null, val total: Long? = null)
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/AppInstallStatus.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | data class AppInstallStatus(val success: Boolean, val id: Int, val snack: Boolean = true)
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/AppInstalled.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | import android.net.Uri
4 | import com.apkupdater.data.aptoide.ApksData
5 | import com.apkupdater.util.toSha1Aptoide
6 |
7 | data class AppInstalled(
8 | val name: String,
9 | val packageName: String,
10 | val version: String,
11 | val versionCode: Long,
12 | val iconUri: Uri = Uri.EMPTY,
13 | val ignored: Boolean = false,
14 | val signature: String = "",
15 | val signatureSha256: String = ""
16 | )
17 |
18 | fun List.getApp(packageName: String) = find { packageName == it.packageName }
19 |
20 | fun List.getVersionCode(packageName: String) = getApp(packageName)
21 | ?.versionCode
22 | ?: 0L
23 |
24 | fun List.getVersion(packageName: String) = getApp(packageName)
25 | ?.version
26 | ?: ""
27 |
28 | fun List.getSignature(packageName: String) = getApp(packageName)
29 | ?.signature
30 | .orEmpty()
31 |
32 | fun List.getPackageNames() = filter { !it.ignored }.map { it.packageName }
33 |
34 | fun AppInstalled.toApksData() = ApksData(
35 | packageName = packageName,
36 | vercode = versionCode.toString(),
37 | signature = signature.toSha1Aptoide(),
38 | isEnabled = true
39 | )
40 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/AppUpdate.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | import android.net.Uri
4 |
5 | data class AppUpdate(
6 | val name: String,
7 | val packageName: String,
8 | val version: String,
9 | val oldVersion: String,
10 | val versionCode: Long,
11 | val oldVersionCode: Long,
12 | val source: Source,
13 | val iconUri: Uri = Uri.EMPTY,
14 | val link: Link = Link.Empty,
15 | val whatsNew: String = "",
16 | val isInstalling: Boolean = false,
17 | val total: Long = 0L,
18 | val progress: Long = 0L,
19 | val id: Int = "${source.name}.$packageName.$versionCode.$version".hashCode()
20 | )
21 |
22 | fun List.indexOf(id: Int) = indexOfFirst { it.id == id }
23 |
24 | fun MutableList.setIsInstalling(id: Int, b: Boolean): List {
25 | val index = this.indexOf(id)
26 | if (index != -1) {
27 | this[index] = this[index].copy(isInstalling = b)
28 | }
29 | return this
30 | }
31 |
32 | fun MutableList.removeId(id: Int): List {
33 | val index = this.indexOf(id)
34 | if (index != -1) this.removeAt(index)
35 | return this
36 | }
37 |
38 | fun MutableList.setProgress(progress: AppInstallProgress): MutableList {
39 | val index = this.indexOf(progress.id)
40 | if (index != -1) {
41 | progress.progress?.let { this[index] = this[index].copy(progress = it) }
42 | progress.total?.let { this[index] = this[index].copy(total = it) }
43 | }
44 | return this
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/AppsUiState.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 |
4 | sealed class AppsUiState {
5 |
6 | data class Loading(
7 | val excludeSystem: Boolean,
8 | val excludeAppStore: Boolean,
9 | val excludeDisabled: Boolean
10 | ): AppsUiState()
11 |
12 | data object Error : AppsUiState()
13 |
14 | data class Success(
15 | val apps: List,
16 | val excludeSystem: Boolean,
17 | val excludeAppStore: Boolean,
18 | val excludeDisabled: Boolean
19 | ): AppsUiState()
20 |
21 | inline fun onLoading(block: (Loading) -> Unit): AppsUiState {
22 | if (this is Loading) block(this)
23 | return this
24 | }
25 |
26 | inline fun onError(block: (Error) -> Unit): AppsUiState {
27 | if (this is Error) block(this)
28 | return this
29 | }
30 |
31 | inline fun onSuccess(block: (Success) -> Unit): AppsUiState {
32 | if (this is Success) block(this)
33 | return this
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/Link.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | import com.aurora.gplayapi.data.models.File
4 |
5 |
6 | sealed class Link {
7 | data object Empty: Link()
8 | data class Url(val link: String, val size: Long = 0L): Link()
9 | data class Xapk(val link: String): Link()
10 | data class Play(val getInstallFiles: () -> List): Link()
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/Screen.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.Home
6 | import androidx.compose.material.icons.filled.Search
7 | import androidx.compose.material.icons.filled.Settings
8 | import androidx.compose.material.icons.filled.ThumbUp
9 | import androidx.compose.material.icons.outlined.Home
10 | import androidx.compose.material.icons.outlined.Search
11 | import androidx.compose.material.icons.outlined.Settings
12 | import androidx.compose.material.icons.outlined.ThumbUp
13 | import androidx.compose.ui.graphics.vector.ImageVector
14 | import com.apkupdater.R
15 |
16 | sealed class Screen(
17 | val route: String,
18 | @StringRes val resourceId: Int,
19 | val icon: ImageVector,
20 | val iconSelected: ImageVector
21 | ) {
22 | data object Apps : Screen("apps", R.string.tab_apps, Icons.Outlined.Home, Icons.Filled.Home)
23 | data object Search : Screen("search", R.string.tab_search, Icons.Outlined.Search, Icons.Filled.Search)
24 | data object Updates : Screen("updates", R.string.tab_updates, Icons.Outlined.ThumbUp, Icons.Filled.ThumbUp)
25 | data object Settings : Screen("settings", R.string.tab_settings, Icons.Outlined.Settings, Icons.Filled.Settings)
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/SearchUiState.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 |
4 | sealed class SearchUiState {
5 | data object Loading: SearchUiState()
6 | data object Error : SearchUiState()
7 | data class Success(val updates: List): SearchUiState()
8 |
9 | inline fun onLoading(block: (Loading) -> Unit): SearchUiState {
10 | if (this is Loading) block(this)
11 | return this
12 | }
13 |
14 | inline fun onError(block: (Error) -> Unit): SearchUiState {
15 | if (this is Error) block(this)
16 | return this
17 | }
18 |
19 | inline fun onSuccess(block: (Success) -> Unit): SearchUiState {
20 | if (this is Success) block(this)
21 | return this
22 | }
23 |
24 | fun mutableUpdates(): MutableList {
25 | if (this is Success) {
26 | return updates.toMutableList()
27 | }
28 | return mutableListOf()
29 | }
30 |
31 | fun updates(): List {
32 | if (this is Success) {
33 | return updates
34 | }
35 | return emptyList()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/SettingsUiState.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 |
4 | sealed class SettingsUiState {
5 | object Settings : SettingsUiState()
6 | object About : SettingsUiState()
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/Source.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 | import com.apkupdater.R
4 |
5 |
6 | data class Source(
7 | val name: String,
8 | val resourceId: Int
9 | )
10 |
11 | val ApkMirrorSource = Source("ApkMirror", R.drawable.ic_apkmirror)
12 | val GitHubSource = Source("GitHub", R.drawable.ic_github)
13 | val FdroidSource = Source("F-Droid (Main)", R.drawable.ic_fdroid)
14 | val IzzySource = Source("F-Droid (Izzy)", R.drawable.ic_izzy)
15 | val AptoideSource = Source("Aptoide", R.drawable.ic_aptoide)
16 | val ApkPureSource = Source("ApkPure", R.drawable.ic_apkpure)
17 | val GitLabSource = Source("GitLab", R.drawable.ic_gitlab)
18 | val PlaySource = Source("Play", R.drawable.ic_play)
19 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/data/ui/UpdatesUiState.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.data.ui
2 |
3 |
4 | sealed class UpdatesUiState {
5 | data object Loading: UpdatesUiState()
6 | data object Error : UpdatesUiState()
7 | data class Success(val updates: List): UpdatesUiState()
8 |
9 | inline fun onLoading(block: (Loading) -> Unit): UpdatesUiState {
10 | if (this is Loading) block(this)
11 | return this
12 | }
13 |
14 | inline fun onError(block: (Error) -> Unit): UpdatesUiState {
15 | if (this is Error) block(this)
16 | return this
17 | }
18 |
19 | inline fun onSuccess(block: (Success) -> Unit): UpdatesUiState {
20 | if (this is Success) block(this)
21 | return this
22 | }
23 |
24 | fun mutableUpdates(): MutableList {
25 | if (this is Success) {
26 | return updates.toMutableList()
27 | }
28 | return mutableListOf()
29 | }
30 |
31 | fun updates(): List {
32 | if (this is Success) {
33 | return updates
34 | }
35 | return emptyList()
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/prefs/Prefs.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.prefs
2 |
3 | import com.apkupdater.data.ui.Screen
4 | import com.aurora.gplayapi.data.models.AuthData
5 | import com.kryptoprefs.context.KryptoContext
6 | import com.kryptoprefs.gson.json
7 | import com.kryptoprefs.preferences.KryptoPrefs
8 |
9 |
10 | class Prefs(
11 | prefs: KryptoPrefs,
12 | isAndroidTv: Boolean
13 | ): KryptoContext(prefs) {
14 | val ignoredApps = json("ignoredApps", emptyList(), true)
15 | val ignoredVersions = json("ignoredVersions", emptyList(), true)
16 | val excludeSystem = boolean("excludeSystem", defValue = true, backed = true)
17 | val excludeDisabled = boolean("excludeDisabled", defValue = true, backed = true)
18 | val excludeStore = boolean("excludeStore", defValue = false, backed = true)
19 | val portraitColumns = int("portraitColumns", 3, true)
20 | val landscapeColumns = int("landscapeColumns", 6, true)
21 | val playTextAnimations = boolean("playTextAnimations", defValue = true, backed = true)
22 | val ignoreAlpha = boolean("ignoreAlpha", defValue = true, backed = true)
23 | val ignoreBeta = boolean("ignoreBeta", defValue = true, backed = true)
24 | val ignorePreRelease = boolean("ignorePreRelease", defValue = true, backed = true)
25 | val useSafeStores = boolean("useSafeStores", defValue = true, backed = true)
26 | val useApkMirror = boolean("useApkMirror", defValue = false, backed = true)
27 | val useGitHub = boolean("useGitHub", defValue = true, backed = true)
28 | val useGitLab = boolean("useGitLab", defValue = true, backed = true)
29 | val useFdroid = boolean("useFdroid", defValue = true, backed = true)
30 | val useIzzy = boolean("useIzzy", defValue = true, backed = true)
31 | val useAptoide = boolean("useAptoide", defValue = true, backed = true)
32 | val useApkPure = boolean("useApkPure", defValue = true, backed = true)
33 | val usePlay = boolean("usePlay", defValue = true, backed = true)
34 | val enableAlarm = boolean("enableAlarm", defValue = false, backed = true)
35 | val alarmHour = int("alarmHour", defValue = 12, backed = true)
36 | val alarmFrequency = int("alarmFrequency", 0, backed = true)
37 | val androidTvUi = boolean("androidTvUi", defValue = true, backed = true)
38 | val rootInstall = boolean("rootInstall", defValue = false, backed = true)
39 | val theme = int("theme", defValue = 0, backed = true)
40 | val lastTab = string("lastTab", defValue = Screen.Updates.route, backed = true)
41 | val playAuthData = json("playAuthData", AuthData("", ""), true)
42 | val lastPlayCheck = long("lastPlayCheck", 0L, true)
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/ApkPureRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.util.Log
4 | import com.apkupdater.data.apkpure.AppInfoForUpdate
5 | import com.apkupdater.data.apkpure.AppUpdateResponse
6 | import com.apkupdater.data.apkpure.DeviceHeader
7 | import com.apkupdater.data.apkpure.GetAppUpdate
8 | import com.apkupdater.data.apkpure.toAppUpdate
9 | import com.apkupdater.data.ui.AppInstalled
10 | import com.apkupdater.data.ui.getApp
11 | import com.apkupdater.data.ui.getSignature
12 | import com.apkupdater.prefs.Prefs
13 | import com.apkupdater.service.ApkPureService
14 | import com.google.gson.Gson
15 | import kotlinx.coroutines.flow.catch
16 | import kotlinx.coroutines.flow.flow
17 |
18 |
19 | class ApkPureRepository(
20 | gson: Gson,
21 | private val service: ApkPureService,
22 | private val prefs: Prefs
23 | ) {
24 |
25 | private val header = gson.toJson(DeviceHeader())
26 |
27 | suspend fun updates(apps: List) = flow {
28 | val info = apps.map { AppInfoForUpdate(it.packageName, it.versionCode) }
29 | val r = service.getAppUpdate(header, GetAppUpdate(info))
30 | val updates = r.app_update_response
31 | .filter { filterSignature(it.sign, apps.getSignature(it.package_name)) }
32 | .filter { filterAlpha(it) }
33 | .filter { filterBeta(it) }
34 | .map { it.toAppUpdate(apps.getApp(it.package_name)) }
35 | emit(updates)
36 | }.catch {
37 | Log.e("ApkPureRepository", it.message, it)
38 | emit(emptyList())
39 | }
40 |
41 | suspend fun search(text: String) = flow {
42 | val response = service.search(header, text)
43 | val info = response.data.data.mapNotNull { d ->
44 | d.data.firstOrNull()?.takeIf { !it.ad }?.app_info?.let {
45 | AppInfoForUpdate(it.package_name, 0L, false)
46 | }
47 | }
48 | val r = service.getAppUpdate(header, GetAppUpdate(info))
49 | val updates = r.app_update_response
50 | .filter { filterAlpha(it) }
51 | .filter { filterBeta(it) }
52 | .map { it.toAppUpdate(null) }
53 | emit(Result.success(updates))
54 | }.catch {
55 | Log.e("ApkPureRepository", it.message, it)
56 | emit(Result.failure(it))
57 | }
58 |
59 | private fun filterAlpha(update: AppUpdateResponse) = when {
60 | prefs.ignoreAlpha.get() && update.version_name.contains("alpha", true) -> false
61 | else -> true
62 | }
63 |
64 | private fun filterBeta(update: AppUpdateResponse) = when {
65 | prefs.ignoreBeta.get() && update.version_name.contains("beta", true) -> false
66 | else -> true
67 | }
68 |
69 | private fun filterSignature(signatures: List, signature: String) = when {
70 | signatures.isEmpty() -> true
71 | signatures.contains(signature) -> true
72 | else -> false
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/AppsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.content.Context
4 | import android.content.pm.ApplicationInfo
5 | import android.content.pm.PackageManager
6 | import android.os.Build
7 | import android.util.Log
8 | import com.apkupdater.prefs.Prefs
9 | import com.apkupdater.transform.toAppInstalled
10 | import com.apkupdater.util.orFalse
11 | import kotlinx.coroutines.flow.catch
12 | import kotlinx.coroutines.flow.flow
13 |
14 |
15 | class AppsRepository(
16 | private val context: Context,
17 | private val prefs: Prefs
18 | ) {
19 |
20 | suspend fun getApps() = flow {
21 | val apps = context.packageManager
22 | .getInstalledPackages(PackageManager.MATCH_ALL + getSignatureFlag())
23 | .asSequence()
24 | .filter { !excludeSystem() || it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
25 | .filter { !excludeSystem() || it.applicationInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP == 0 }
26 | .filter { !excludeDisabled() || it.applicationInfo.enabled }
27 | .filter { !excludeStore() || !isAppStore(getInstallerPackageName(it.packageName)) }
28 | .map { it.toAppInstalled(context, ignoredApps()) }
29 | .sortedBy { it.name }
30 | .sortedBy { it.ignored }
31 | .toList()
32 | emit(Result.success(apps))
33 | }.catch {
34 | Log.e("AppsRepository", "Error getting apps.", it)
35 | emit(Result.failure(it))
36 | }
37 |
38 | private fun excludeSystem() = prefs.excludeSystem.get()
39 | private fun excludeDisabled() = prefs.excludeDisabled.get()
40 | private fun excludeStore() = prefs.excludeStore.get()
41 | private fun ignoredApps() = prefs.ignoredApps.get()
42 |
43 | @Suppress("DEPRECATION")
44 | private fun getSignatureFlag(): Int {
45 | return if (Build.VERSION.SDK_INT >= 28) {
46 | PackageManager.GET_SIGNING_CERTIFICATES
47 | } else {
48 | PackageManager.GET_SIGNATURES
49 | }
50 | }
51 |
52 | @Suppress("DEPRECATION")
53 | private fun getInstallerPackageName(packageName: String): String {
54 | return if (Build.VERSION.SDK_INT < 30) {
55 | context.packageManager.getInstallerPackageName(packageName).orEmpty()
56 | } else {
57 | context.packageManager.getInstallSourceInfo(packageName).installingPackageName.orEmpty()
58 | }
59 | }
60 |
61 | // Checks if Play Store or Amazon Store
62 | private fun isAppStore(name: String?) = name?.contains("com.android.vending").orFalse()
63 | || name?.contains("com.amazon").orFalse()
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/FdroidRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.os.Build
4 | import android.util.Log
5 | import com.apkupdater.data.fdroid.FdroidApp
6 | import com.apkupdater.data.fdroid.FdroidData
7 | import com.apkupdater.data.fdroid.FdroidUpdate
8 | import com.apkupdater.data.fdroid.toAppUpdate
9 | import com.apkupdater.data.ui.AppInstalled
10 | import com.apkupdater.data.ui.Source
11 | import com.apkupdater.data.ui.getApp
12 | import com.apkupdater.data.ui.getVersionCode
13 | import com.apkupdater.prefs.Prefs
14 | import com.apkupdater.service.FdroidService
15 | import com.google.gson.Gson
16 | import kotlinx.coroutines.flow.catch
17 | import kotlinx.coroutines.flow.flow
18 | import java.io.InputStream
19 | import java.util.jar.JarInputStream
20 |
21 |
22 | class FdroidRepository(
23 | private val service: FdroidService,
24 | private val url: String,
25 | private val source: Source,
26 | private val prefs: Prefs
27 | ) {
28 | private val arch = Build.SUPPORTED_ABIS.toSet()
29 | private val api = Build.VERSION.SDK_INT
30 |
31 | suspend fun updates(apps: List) = flow {
32 | val response = service.getJar("${url}index-v1.jar")
33 | val data = jarToJson(response.byteStream())
34 | val appNames = apps.map { it.packageName }
35 | val updates = data.apps
36 | .asSequence()
37 | .filter { appNames.contains(it.packageName) }
38 | .filter { filterSignature(apps.getApp(it.packageName)!!, it) }
39 | .map { FdroidUpdate(data.packages[it.packageName]!![0], it) }
40 | .filter { it.apk.versionCode > apps.getVersionCode(it.app.packageName) }
41 | .parseUpdates(apps)
42 | emit(updates)
43 | }.catch {
44 | emit(emptyList())
45 | Log.e("FdroidRepository", "Error looking for updates.", it)
46 | }
47 |
48 | suspend fun search(text: String) = flow {
49 | val response = service.getJar("${url}index-v1.jar")
50 | val data = jarToJson(response.byteStream())
51 | val updates = data.apps
52 | .asSequence()
53 | .map { FdroidUpdate(data.packages[it.packageName]!![0], it) }
54 | .filter { it.app.name.contains(text, true) || it.app.packageName.contains(text, true) || it.apk.apkName.contains(text, true) }
55 | .parseUpdates(null)
56 | emit(Result.success(updates))
57 | }.catch {
58 | emit(Result.failure(it))
59 | Log.e("FdroidRepository", "Error searching.", it)
60 | }
61 |
62 | private fun Sequence.parseUpdates(apps: List?) = this
63 | .filter { it.apk.minSdkVersion <= api }
64 | .filter { filterArch(it) }
65 | .filter { filterAlpha(it) }
66 | .filter { filterBeta(it) }
67 | .map { it.toAppUpdate(apps?.getApp(it.app.packageName), source, url) }
68 | .toList()
69 |
70 | private fun filterSignature(installed: AppInstalled, update: FdroidApp) = when {
71 | update.allowedAPKSigningKeys.isEmpty() -> true
72 | update.allowedAPKSigningKeys.contains(installed.signatureSha256) -> true
73 | else -> false
74 | }
75 |
76 | private fun filterAlpha(update: FdroidUpdate) = when {
77 | prefs.ignoreAlpha.get() && update.apk.versionName.contains("alpha", true) -> false
78 | else -> true
79 | }
80 |
81 | private fun filterBeta(update: FdroidUpdate) = when {
82 | prefs.ignoreBeta.get() && update.apk.versionName.contains("beta", true) -> false
83 | else -> true
84 | }
85 |
86 | private fun filterArch(update: FdroidUpdate) = when {
87 | update.apk.nativecode.isEmpty() -> true
88 | update.apk.nativecode.intersect(arch).isNotEmpty() -> true
89 | else -> false
90 | }
91 |
92 | private fun jarToJson(stream: InputStream): FdroidData {
93 | val jar = JarInputStream(stream)
94 | var entry = jar.nextJarEntry
95 | while (entry != null) {
96 | if (entry.name == "index-v1.json") {
97 | return Gson().fromJson(jar.reader(), FdroidData::class.java)
98 | }
99 | entry = jar.nextJarEntry
100 | }
101 | return FdroidData()
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/GitLabRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.net.Uri
4 | import android.util.Log
5 | import com.apkupdater.data.gitlab.GitLabApps
6 | import com.apkupdater.data.gitlab.GitLabRelease
7 | import com.apkupdater.data.ui.AppInstalled
8 | import com.apkupdater.data.ui.AppUpdate
9 | import com.apkupdater.data.ui.GitLabSource
10 | import com.apkupdater.data.ui.Link
11 | import com.apkupdater.data.ui.getApp
12 | import com.apkupdater.prefs.Prefs
13 | import com.apkupdater.service.GitLabService
14 | import com.apkupdater.util.combine
15 | import com.apkupdater.util.filterVersionTag
16 | import io.github.g00fy2.versioncompare.Version
17 | import kotlinx.coroutines.flow.Flow
18 | import kotlinx.coroutines.flow.catch
19 | import kotlinx.coroutines.flow.collect
20 | import kotlinx.coroutines.flow.flow
21 |
22 |
23 | class GitLabRepository(
24 | private val service: GitLabService,
25 | private val prefs: Prefs
26 | ) {
27 |
28 | suspend fun updates(apps: List) = flow {
29 | val checks = mutableListOf>>()
30 | GitLabApps.forEach { app ->
31 | apps.find { it.packageName == app.packageName }?.let {
32 | checks.add(checkApp(apps, app.user, app.repo, app.packageName, it.version, null))
33 | }
34 | }
35 | if (checks.isEmpty()) {
36 | emit(emptyList())
37 | } else {
38 | checks.combine { all -> emit(all.flatMap { it }) }.collect()
39 | }
40 | }
41 |
42 | private suspend fun checkApp(
43 | apps: List?,
44 | user: String,
45 | repo: String,
46 | packageName: String,
47 | currentVersion: String,
48 | extra: Regex?
49 | ) = flow {
50 | val releases = service.getReleases(user, repo)
51 | .filter { Version(filterVersionTag(it.tag_name)) > Version(currentVersion) }
52 |
53 | if (releases.isNotEmpty()) {
54 | val app = apps?.getApp(packageName)
55 | emit(listOf(
56 | AppUpdate(
57 | name = repo,
58 | packageName = packageName,
59 | version = releases[0].tag_name,
60 | oldVersion = app?.version ?: "?",
61 | versionCode = 0L,
62 | oldVersionCode = app?.versionCode ?: 0L,
63 | source = GitLabSource,
64 | link = Link.Url(getApkUrl(packageName, releases[0])),
65 | whatsNew = releases[0].description,
66 | iconUri = if (apps == null) Uri.parse(releases[0].author.avatar_url) else Uri.EMPTY
67 | )))
68 | } else {
69 | emit(emptyList())
70 | }
71 | }.catch {
72 | emit(emptyList())
73 | Log.e("GitLabRepository", "Error fetching releases for $packageName.", it)
74 | }
75 |
76 | suspend fun search(text: String) = flow {
77 | val checks = mutableListOf>>()
78 |
79 | GitLabApps.forEach { app ->
80 | if (app.repo.contains(text, true) || app.user.contains(text, true) || app.packageName.contains(text, true)) {
81 | checks.add(checkApp(null, app.user, app.repo, app.packageName, "?", null))
82 | }
83 | }
84 |
85 | if (checks.isEmpty()) {
86 | emit(Result.success(emptyList()))
87 | } else {
88 | checks.combine { all ->
89 | val r = all.flatMap { it }
90 | emit(Result.success(r))
91 | }.collect()
92 | }
93 | }.catch {
94 | emit(Result.failure(it))
95 | Log.e("GitLabRepository", "Error searching.", it)
96 | }
97 |
98 | private fun getApkUrl(
99 | packageName: String,
100 | release: GitLabRelease
101 | ): String {
102 | // TODO: Take into account arch
103 | val source = release.assets.sources.find { it.url.endsWith(".apk", true) }
104 | if (source != null) return source.url
105 |
106 | val link = release.assets.links.find { it.url.endsWith(".apk", true) }
107 | if (link != null) return link.url
108 |
109 | return ""
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/SearchRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.util.Log
4 | import com.apkupdater.data.ui.AppUpdate
5 | import com.apkupdater.prefs.Prefs
6 | import com.apkupdater.util.combine
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.catch
9 | import kotlinx.coroutines.flow.collect
10 | import kotlinx.coroutines.flow.flow
11 |
12 | class SearchRepository(
13 | private val apkMirrorRepository: ApkMirrorRepository,
14 | private val fdroidRepository: FdroidRepository,
15 | private val izzyRepository: FdroidRepository,
16 | private val aptoideRepository: AptoideRepository,
17 | private val gitHubRepository: GitHubRepository,
18 | private val apkPureRepository: ApkPureRepository,
19 | private val gitLabRepository: GitLabRepository,
20 | private val playRepository: PlayRepository,
21 | private val prefs: Prefs
22 | ) {
23 |
24 | fun search(text: String) = flow {
25 | val sources = mutableListOf>>>()
26 | if (prefs.useApkMirror.get()) sources.add(apkMirrorRepository.search(text))
27 | if (prefs.useFdroid.get()) sources.add(fdroidRepository.search(text))
28 | if (prefs.useIzzy.get()) sources.add(izzyRepository.search(text))
29 | if (prefs.useAptoide.get()) sources.add(aptoideRepository.search(text))
30 | if (prefs.useGitHub.get()) sources.add(gitHubRepository.search(text))
31 | if (prefs.useApkPure.get()) sources.add(apkPureRepository.search(text))
32 | if (prefs.useGitLab.get()) sources.add(gitLabRepository.search(text))
33 | if (prefs.usePlay.get()) sources.add(playRepository.search(text))
34 |
35 | if (sources.isNotEmpty()) {
36 | sources.combine { updates ->
37 | val result = updates.filter { it.isSuccess }.mapNotNull { it.getOrNull() }
38 | emit(Result.success(result.flatten().sortedBy { it.name }))
39 | }.collect()
40 | } else {
41 | emit(Result.success(emptyList()))
42 | }
43 | }.catch {
44 | emit(Result.failure(it))
45 | Log.e("SearchRepository", "Error searching.", it)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/repository/UpdatesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.repository
2 |
3 | import android.util.Log
4 | import com.apkupdater.data.ui.AppUpdate
5 | import com.apkupdater.prefs.Prefs
6 | import com.apkupdater.util.combine
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.catch
9 | import kotlinx.coroutines.flow.collect
10 | import kotlinx.coroutines.flow.flow
11 |
12 |
13 | class UpdatesRepository(
14 | private val appsRepository: AppsRepository,
15 | private val apkMirrorRepository: ApkMirrorRepository,
16 | private val gitHubRepository: GitHubRepository,
17 | private val fdroidRepository: FdroidRepository,
18 | private val izzyRepository: FdroidRepository,
19 | private val aptoideRepository: AptoideRepository,
20 | private val apkPureRepository: ApkPureRepository,
21 | private val gitLabRepository: GitLabRepository,
22 | private val playRepository: PlayRepository,
23 | private val prefs: Prefs
24 | ) {
25 |
26 | fun updates() = flow> {
27 | appsRepository.getApps().collect { result ->
28 | result.onSuccess { apps ->
29 | val filtered = apps.filter { !it.ignored }
30 | val sources = mutableListOf>>()
31 | if (prefs.useApkMirror.get()) sources.add(apkMirrorRepository.updates(filtered))
32 | if (prefs.useGitHub.get()) sources.add(gitHubRepository.updates(filtered))
33 | if (prefs.useFdroid.get()) sources.add(fdroidRepository.updates(filtered))
34 | if (prefs.useIzzy.get()) sources.add(izzyRepository.updates(filtered))
35 | if (prefs.useAptoide.get()) sources.add(aptoideRepository.updates(filtered))
36 | if (prefs.useApkPure.get()) sources.add(apkPureRepository.updates(filtered))
37 | if (prefs.useGitLab.get()) sources.add(gitLabRepository.updates(filtered))
38 | if (prefs.usePlay.get()) sources.add(playRepository.updates(filtered))
39 |
40 | if (sources.isNotEmpty()) {
41 | sources
42 | .combine { updates -> emit(updates.flatMap { it }) }
43 | .collect()
44 | } else {
45 | emit(emptyList())
46 | }
47 | }.onFailure {
48 | Log.e("UpdatesRepository", "Error getting apps", it)
49 | }
50 | }
51 | }.catch {
52 | Log.e("UpdatesRepository", "Error getting updates", it)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/ApkMirrorService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import com.apkupdater.data.apkmirror.AppExistsRequest
4 | import com.apkupdater.data.apkmirror.AppExistsResponse
5 | import com.apkupdater.BuildConfig
6 | import retrofit2.http.Body
7 | import retrofit2.http.Headers
8 | import retrofit2.http.POST
9 |
10 |
11 | interface ApkMirrorService {
12 |
13 | @Headers(
14 | "User-Agent: APKUpdater-v" + BuildConfig.VERSION_NAME,
15 | "Authorization: Basic YXBpLWFwa3VwZGF0ZXI6cm01cmNmcnVVakt5MDRzTXB5TVBKWFc4",
16 | "Content-Type: application/json"
17 | )
18 | @POST("/wp-json/apkm/v1/app_exists/")
19 | suspend fun appExists(
20 | @Body request: AppExistsRequest
21 | ): AppExistsResponse
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/ApkPureService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import com.apkupdater.data.apkpure.GetAppUpdate
4 | import com.apkupdater.data.apkpure.GetAppUpdateResponse
5 | import com.apkupdater.data.apkpure.SearchResponse
6 | import retrofit2.http.Body
7 | import retrofit2.http.GET
8 | import retrofit2.http.Header
9 | import retrofit2.http.Headers
10 | import retrofit2.http.POST
11 | import retrofit2.http.Query
12 |
13 |
14 | interface ApkPureService {
15 |
16 | @Headers(
17 | "content-type: application/json",
18 | "ual-access-businessid: projecta"
19 | )
20 | @POST("v3/get_app_update")
21 | suspend fun getAppUpdate(
22 | @Header("ual-access-projecta") header: String,
23 | @Body request: GetAppUpdate
24 | ): GetAppUpdateResponse
25 |
26 | @Headers(
27 | "ual-access-businessid: projecta",
28 | )
29 | @GET("v3/search_query_new")
30 | suspend fun search(
31 | @Header("ual-access-projecta") header: String,
32 | @Query("key") key: String
33 | ): SearchResponse
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/AptoideService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import com.apkupdater.data.aptoide.ListAppUpdatesResponse
4 | import com.apkupdater.data.aptoide.ListAppsUpdatesRequest
5 | import com.apkupdater.data.aptoide.ListSearchAppsRequest
6 | import com.apkupdater.data.aptoide.ListSearchAppsResponse
7 | import retrofit2.http.Body
8 | import retrofit2.http.Header
9 | import retrofit2.http.POST
10 | import retrofit2.http.Query
11 |
12 | interface AptoideService {
13 |
14 | @POST("listSearchApps")
15 | suspend fun searchApps(@Body request: ListSearchAppsRequest): ListSearchAppsResponse
16 |
17 | @POST("listAppsUpdates")
18 | suspend fun findUpdates(
19 | @Body request: ListAppsUpdatesRequest,
20 | @Header("X-Bypass-Cache") bypassCache: Boolean = true,
21 | @Query("aab") showAabs: Boolean = false
22 | ): ListAppUpdatesResponse
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/FdroidService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import okhttp3.ResponseBody
4 | import retrofit2.http.GET
5 | import retrofit2.http.Streaming
6 | import retrofit2.http.Url
7 |
8 | interface FdroidService {
9 |
10 | @Streaming
11 | @GET
12 | suspend fun getJar(@Url url: String): ResponseBody
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/GitHubService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import com.apkupdater.data.github.GitHubRelease
4 | import retrofit2.http.GET
5 | import retrofit2.http.Path
6 |
7 | interface GitHubService {
8 |
9 | @GET("/repos/{user}/{repo}/releases")
10 | suspend fun getReleases(
11 | @Path("user") user: String = "rumboalla",
12 | @Path("repo") repo: String = "apkupdater"
13 | ): List
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/service/GitLabService.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.service
2 |
3 | import com.apkupdater.data.gitlab.GitLabRelease
4 | import retrofit2.http.GET
5 | import retrofit2.http.Path
6 |
7 |
8 | interface GitLabService {
9 |
10 | @GET("api/v4/projects/{user}%2F{repo}/releases")
11 | suspend fun getReleases(
12 | @Path("user") user: String,
13 | @Path("repo") repo: String
14 | ): List
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/transform/Transforms.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.transform
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageInfo
5 | import android.net.Uri
6 | import android.os.Build
7 | import com.apkupdater.data.ui.AppInstalled
8 | import com.apkupdater.util.getSignatureSha1
9 | import com.apkupdater.util.getSignatureSha256
10 | import com.apkupdater.util.name
11 |
12 |
13 | @Suppress("DEPRECATION")
14 | fun PackageInfo.toAppInstalled(context: Context, ignored: List) = AppInstalled(
15 | name(context),
16 | packageName,
17 | versionName.orEmpty(),
18 | if (Build.VERSION.SDK_INT >= 28) longVersionCode else versionCode.toLong(),
19 | iconUri(packageName, applicationInfo.icon),
20 | ignored.contains(packageName),
21 | getSignatureSha1(),
22 | getSignatureSha256()
23 | )
24 |
25 | fun iconUri(packageName: String, id: Int): Uri = Uri.parse("android.resource://$packageName/$id")
26 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/activity/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.activity
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import com.apkupdater.ui.screen.MainScreen
7 |
8 |
9 | class MainActivity : ComponentActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContent { MainScreen() }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/component/Grid.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.component
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.lazy.grid.GridCells
11 | import androidx.compose.foundation.lazy.grid.LazyGridScope
12 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.platform.LocalConfiguration
17 | import androidx.compose.ui.unit.dp
18 | import androidx.tv.foundation.lazy.grid.TvGridCells
19 | import androidx.tv.foundation.lazy.grid.TvLazyGridScope
20 | import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid
21 | import com.apkupdater.prefs.Prefs
22 | import org.koin.androidx.compose.get
23 |
24 | @Composable
25 | fun LoadingGrid() {
26 | if (get().androidTvUi.get()) {
27 | TvShimmeringGrid()
28 | } else {
29 | ShimmeringGrid()
30 | }
31 | }
32 |
33 | @Composable
34 | fun ShimmeringGrid() = InstalledGrid(false) {
35 | items(16) {
36 | Box(Modifier.height(170.dp).shimmering(true))
37 | }
38 | }
39 |
40 | @Composable
41 | fun TvShimmeringGrid() = TvInstalledGrid(false) {
42 | items(16) {
43 | Box(Modifier.height(155.dp).shimmering(true))
44 | }
45 | }
46 |
47 | @Composable
48 | fun EmptyGrid(
49 | text: String = ""
50 | ) = Box(Modifier.fillMaxSize()) {
51 | if (text.isNotEmpty()) {
52 | MediumTitle(text, Modifier.align(Alignment.Center))
53 | }
54 | LazyColumn(Modifier.fillMaxSize()) {}
55 | }
56 |
57 | @Composable
58 | fun InstalledGrid(
59 | scroll: Boolean = true,
60 | content: LazyGridScope.() -> Unit
61 | ) = LazyVerticalGrid(
62 | columns = GridCells.Fixed(getNumColumns(LocalConfiguration.current.orientation)),
63 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp),
64 | verticalArrangement = Arrangement.spacedBy(8.dp),
65 | horizontalArrangement = Arrangement.spacedBy(8.dp),
66 | content = content,
67 | userScrollEnabled = scroll,
68 | modifier = Modifier.fillMaxSize()
69 | )
70 |
71 | @Composable
72 | fun TvInstalledGrid(scroll: Boolean = true, content: TvLazyGridScope.() -> Unit) = TvLazyVerticalGrid(
73 | columns = TvGridCells.Fixed(getTvNumColumns()),
74 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp),
75 | verticalArrangement = Arrangement.spacedBy(8.dp),
76 | horizontalArrangement = Arrangement.spacedBy(8.dp),
77 | content = content,
78 | userScrollEnabled = scroll,
79 | modifier = Modifier.fillMaxSize()
80 | )
81 |
82 | @Composable
83 | fun getNumColumns(orientation: Int): Int {
84 | val prefs = get()
85 | return if(orientation == Configuration.ORIENTATION_PORTRAIT)
86 | prefs.portraitColumns.get()
87 | else
88 | prefs.landscapeColumns.get()
89 | }
90 |
91 | @Composable
92 | fun getTvNumColumns(): Int {
93 | return if(LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT)
94 | 1
95 | else
96 | 2
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/component/Image.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.component
2 |
3 | import android.net.Uri
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.layout.ContentScale
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.unit.dp
18 | import coil.compose.AsyncImage
19 | import coil.request.ImageRequest
20 | import com.apkupdater.R
21 | import com.apkupdater.util.getAppIcon
22 |
23 | @Composable
24 | private fun BaseLoadingImage(
25 | request: ImageRequest,
26 | modifier: Modifier,
27 | color: Color = Color.Transparent
28 | ) = AsyncImage(
29 | model = request,
30 | contentDescription = stringResource(R.string.app_cd),
31 | modifier = modifier
32 | .padding(10.dp)
33 | .clip(RoundedCornerShape(8.dp))
34 | .background(color),
35 | contentScale = ContentScale.Fit,
36 | error = painterResource(R.drawable.ic_root),
37 | placeholder = painterResource(R.drawable.ic_empty)
38 | )
39 |
40 | @Composable
41 | fun LoadingImage(
42 | uri: Uri,
43 | modifier: Modifier = Modifier.height(120.dp).fillMaxSize(),
44 | crossfade: Boolean = true,
45 | color: Color = Color.Transparent
46 | ) = BaseLoadingImage(
47 | ImageRequest.Builder(LocalContext.current).data(uri).crossfade(crossfade).build(),
48 | modifier,
49 | color
50 | )
51 |
52 | @Composable
53 | fun LoadingImageApp(
54 | packageName: String,
55 | modifier: Modifier = Modifier.height(120.dp).fillMaxSize(),
56 | crossfade: Boolean = false,
57 | color: Color = Color.Transparent
58 | ) = BaseLoadingImage(
59 | ImageRequest.Builder(LocalContext.current).data(LocalContext.current.getAppIcon(packageName)).crossfade(crossfade).build(),
60 | modifier,
61 | color
62 | )
63 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/component/Modifier.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.component
2 |
3 | import androidx.compose.animation.core.animateFloat
4 | import androidx.compose.animation.core.infiniteRepeatable
5 | import androidx.compose.animation.core.rememberInfiniteTransition
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.foundation.background
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.composed
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.graphics.Brush
17 | import androidx.compose.ui.layout.onGloballyPositioned
18 | import androidx.compose.ui.unit.IntSize
19 | import com.apkupdater.ui.theme.statusBarColor
20 |
21 |
22 | fun Modifier.shimmering(enabled: Boolean): Modifier = composed {
23 | if (enabled) {
24 | var size by remember { mutableStateOf(IntSize.Zero) }
25 | val transition = rememberInfiniteTransition("shimmering")
26 | val color = MaterialTheme.colorScheme.statusBarColor()
27 | val startOffsetX by transition.animateFloat(
28 | label = "shimmering",
29 | initialValue = -2 * size.width.toFloat(),
30 | targetValue = 2 * size.width.toFloat(),
31 | animationSpec = infiniteRepeatable(animation = tween(1000))
32 | )
33 | background(
34 | brush = Brush.linearGradient(
35 | colors = listOf(
36 | color.copy(alpha = 0.9f),
37 | color.copy(alpha = 0.3f),
38 | color.copy(alpha = 0.9f)
39 | ),
40 | start = Offset(startOffsetX, 0f),
41 | end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat())
42 | )
43 | ).onGloballyPositioned {
44 | size = it.size
45 | }
46 | } else {
47 | Modifier
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/component/UiComponents.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.component
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.alpha
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.compose.ui.res.stringResource
14 | import androidx.compose.ui.unit.dp
15 | import com.apkupdater.R
16 | import com.apkupdater.data.ui.AppInstalled
17 | import com.apkupdater.data.ui.AppUpdate
18 | import com.apkupdater.data.ui.Link
19 | import com.apkupdater.util.getAppName
20 |
21 |
22 | @Composable
23 | fun AppImage(app: AppInstalled, onIgnore: (String) -> Unit = {}) = Box {
24 | LoadingImageApp(app.packageName)
25 | TextBubble(app.versionCode, Modifier.align(Alignment.BottomStart))
26 | IgnoreIcon(
27 | app.ignored,
28 | { onIgnore(app.packageName) },
29 | Modifier.align(Alignment.TopEnd).padding(4.dp)
30 | )
31 | }
32 |
33 | @Composable
34 | fun UpdateImage(app: AppUpdate, onInstall: (Link) -> Unit = {}) = Box {
35 | LoadingImageApp(app.packageName)
36 | TextBubble(app.versionCode, Modifier.align(Alignment.BottomStart))
37 | InstallProgressIcon(app.isInstalling) { onInstall(app.link) }
38 | SourceIcon(
39 | app.source,
40 | Modifier.align(Alignment.TopStart).padding(4.dp).size(28.dp)
41 | )
42 | }
43 |
44 |
45 | @Composable
46 | fun SearchImage(app: AppUpdate, onInstall: (Link) -> Unit = {}) = Box {
47 | LoadingImage(app.iconUri)
48 | TextBubble(app.versionCode, Modifier.align(Alignment.BottomStart))
49 | InstallProgressIcon(app.isInstalling) { onInstall(app.link) }
50 | SourceIcon(
51 | app.source,
52 | Modifier.align(Alignment.TopStart).padding(4.dp).size(28.dp)
53 | )
54 | }
55 |
56 | @Composable
57 | fun InstalledItem(app: AppInstalled, onIgnore: (String) -> Unit = {}) = Column(
58 | modifier = Modifier.alpha(if (app.ignored) 0.5f else 1f)
59 | ) {
60 | AppImage(app, onIgnore)
61 | Column(Modifier.padding(top = 4.dp)) {
62 | ScrollableText { SmallText(app.packageName) }
63 | MediumTitle(app.name)
64 | }
65 | }
66 |
67 | @Composable
68 | fun UpdateItem(app: AppUpdate, onInstall: (Link) -> Unit = {}) = Column {
69 | UpdateImage(app, onInstall)
70 | Column(Modifier.padding(top = 4.dp)) {
71 | ScrollableText { SmallText(app.packageName) }
72 | MediumTitle(app.name.ifEmpty { LocalContext.current.getAppName(app.packageName) })
73 | }
74 | }
75 |
76 | @Composable
77 | fun SearchItem(app: AppUpdate, onInstall: (Link) -> Unit = {}) = Column {
78 | SearchImage(app, onInstall)
79 | Column(Modifier.padding(top = 4.dp)) {
80 | ScrollableText { SmallText(app.packageName) }
81 | MediumTitle(app.name)
82 | }
83 | }
84 |
85 | @Composable
86 | fun DefaultErrorScreen() = Box(Modifier.fillMaxSize()) {
87 | HugeText(
88 | stringResource(R.string.something_went_wrong),
89 | Modifier.align(Alignment.Center),
90 | 2
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/screen/AppsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.screen
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.lazy.grid.items
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.Home
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Text
14 | import androidx.compose.material3.TopAppBar
15 | import androidx.compose.material3.TopAppBarDefaults
16 | import androidx.compose.material3.minimumInteractiveComponentSize
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
23 | import androidx.tv.foundation.lazy.grid.items
24 | import com.apkupdater.R
25 | import com.apkupdater.data.ui.AppsUiState
26 | import com.apkupdater.prefs.Prefs
27 | import com.apkupdater.ui.component.DefaultErrorScreen
28 | import com.apkupdater.ui.component.ExcludeAppStoreIcon
29 | import com.apkupdater.ui.component.ExcludeDisabledIcon
30 | import com.apkupdater.ui.component.ExcludeSystemIcon
31 | import com.apkupdater.ui.component.InstalledGrid
32 | import com.apkupdater.ui.component.InstalledItem
33 | import com.apkupdater.ui.component.LoadingGrid
34 | import com.apkupdater.ui.component.TvInstalledGrid
35 | import com.apkupdater.ui.component.TvInstalledItem
36 | import com.apkupdater.ui.theme.statusBarColor
37 | import com.apkupdater.viewmodel.AppsViewModel
38 | import org.koin.androidx.compose.get
39 | import org.koin.androidx.compose.koinViewModel
40 |
41 |
42 | @Composable
43 | fun AppsScreen(
44 | viewModel: AppsViewModel = koinViewModel()
45 | ) {
46 | viewModel.state().collectAsStateWithLifecycle().value.onLoading {
47 | AppsScreenLoading(viewModel, it)
48 | }.onError {
49 | AppsScreenError()
50 | }.onSuccess {
51 | AppsScreenSuccess(viewModel, it)
52 | }
53 | }
54 |
55 | @Composable
56 | fun AppsScreenSuccess(viewModel: AppsViewModel, state: AppsUiState.Success) = Column {
57 | AppsTopBar(viewModel, state.excludeSystem, state.excludeAppStore, state.excludeDisabled)
58 | if (get().androidTvUi.get()) {
59 | TvInstalledGrid {
60 | items(state.apps) {
61 | TvInstalledItem(it) { app -> viewModel.ignore(app) }
62 | }
63 | }
64 | } else {
65 | InstalledGrid {
66 | items(state.apps) {
67 | InstalledItem(it) { app -> viewModel.ignore(app) }
68 | }
69 | }
70 | }
71 | }
72 |
73 | @Composable
74 | fun AppsScreenLoading(viewModel: AppsViewModel, state: AppsUiState.Loading) = Column {
75 | AppsTopBar(viewModel, state.excludeSystem, state.excludeAppStore, state.excludeDisabled)
76 | LoadingGrid()
77 | }
78 |
79 | @OptIn(ExperimentalMaterial3Api::class)
80 | @Composable
81 | fun AppsTopBar(
82 | viewModel: AppsViewModel,
83 | excludeSystem: Boolean,
84 | excludeAppStore: Boolean,
85 | excludeDisabled: Boolean
86 | ) = TopAppBar(
87 | title = { Text(stringResource(R.string.tab_apps)) },
88 | colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.statusBarColor()),
89 | actions = {
90 | IconButton(onClick = { viewModel.onSystemClick() }) {
91 | ExcludeSystemIcon(excludeSystem)
92 | }
93 | IconButton(onClick = { viewModel.onAppStoreClick() }) {
94 | ExcludeAppStoreIcon(excludeAppStore)
95 | }
96 | IconButton(onClick = { viewModel.onDisabledClick() }) {
97 | ExcludeDisabledIcon(excludeDisabled)
98 | }
99 | },
100 | navigationIcon = {
101 | Box(Modifier.minimumInteractiveComponentSize().size(40.dp), Alignment.Center) {
102 | Icon(Icons.Filled.Home, "Tab Icon")
103 | }
104 | }
105 | )
106 |
107 | @Composable
108 | fun AppsScreenError() = DefaultErrorScreen()
109 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/screen/UpdatesScreen.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.screen
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.lazy.grid.items
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.ThumbUp
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Text
14 | import androidx.compose.material3.TopAppBar
15 | import androidx.compose.material3.TopAppBarDefaults
16 | import androidx.compose.material3.minimumInteractiveComponentSize
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.platform.LocalUriHandler
21 | import androidx.compose.ui.platform.UriHandler
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.unit.dp
24 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
25 | import androidx.tv.foundation.lazy.grid.items
26 | import com.apkupdater.R
27 | import com.apkupdater.data.ui.AppUpdate
28 | import com.apkupdater.prefs.Prefs
29 | import com.apkupdater.ui.component.DefaultErrorScreen
30 | import com.apkupdater.ui.component.EmptyGrid
31 | import com.apkupdater.ui.component.InstalledGrid
32 | import com.apkupdater.ui.component.LoadingGrid
33 | import com.apkupdater.ui.component.RefreshIcon
34 | import com.apkupdater.ui.component.TvInstalledGrid
35 | import com.apkupdater.ui.component.TvUpdateItem
36 | import com.apkupdater.ui.component.UpdateItem
37 | import com.apkupdater.ui.theme.statusBarColor
38 | import com.apkupdater.viewmodel.UpdatesViewModel
39 | import org.koin.androidx.compose.get
40 |
41 |
42 | @Composable
43 | fun UpdatesScreen(viewModel: UpdatesViewModel) {
44 | viewModel.state().collectAsStateWithLifecycle().value.onLoading {
45 | UpdatesScreenLoading(viewModel)
46 | }.onError {
47 | UpdatesScreenError()
48 | }.onSuccess {
49 | UpdatesScreenSuccess(viewModel, it.updates)
50 | }
51 | }
52 |
53 | @OptIn(ExperimentalMaterial3Api::class)
54 | @Composable
55 | fun UpdatesTopBar(viewModel: UpdatesViewModel) = TopAppBar(
56 | title = {
57 | Text(stringResource(R.string.tab_updates))
58 | },
59 | colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.statusBarColor()),
60 | actions = {
61 | IconButton(onClick = { viewModel.refresh() }) {
62 | RefreshIcon(stringResource(R.string.refresh_updates))
63 | }
64 | },
65 | navigationIcon = {
66 | Box(Modifier.minimumInteractiveComponentSize().size(40.dp), Alignment.Center) {
67 | Icon(Icons.Filled.ThumbUp, "Tab Icon")
68 | }
69 | }
70 | )
71 |
72 | @Composable
73 | fun UpdatesScreenLoading(viewModel: UpdatesViewModel) = Column {
74 | UpdatesTopBar(viewModel)
75 | LoadingGrid()
76 | }
77 |
78 | @Composable
79 | fun UpdatesScreenError() = DefaultErrorScreen()
80 |
81 | @Composable
82 | fun UpdatesScreenSuccess(
83 | viewModel: UpdatesViewModel,
84 | updates: List
85 | ) = Column {
86 | val handler = LocalUriHandler.current
87 | val tv = get().androidTvUi.get()
88 |
89 | UpdatesTopBar(viewModel)
90 |
91 | when {
92 | updates.isEmpty() -> EmptyGrid()
93 | tv -> TvGrid(viewModel, updates, handler)
94 | !tv -> Grid(viewModel, updates, handler)
95 | }
96 | }
97 |
98 | @Composable
99 | fun TvGrid(
100 | viewModel: UpdatesViewModel,
101 | updates: List,
102 | handler: UriHandler
103 | ) = TvInstalledGrid {
104 | items(updates) { update ->
105 | TvUpdateItem(
106 | update,
107 | { viewModel.install(update, handler) },
108 | { viewModel.ignoreVersion(update.id)}
109 | )
110 | }
111 | }
112 |
113 | @Composable
114 | fun Grid(
115 | viewModel: UpdatesViewModel,
116 | updates: List,
117 | handler: UriHandler
118 | ) = InstalledGrid {
119 | items(updates) { update ->
120 | UpdateItem(update) {
121 | viewModel.install(update, handler)
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.material3.ColorScheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.material3.surfaceColorAtElevation
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.SideEffect
14 | import androidx.compose.ui.graphics.toArgb
15 | import androidx.compose.ui.platform.LocalContext
16 | import androidx.compose.ui.platform.LocalView
17 | import androidx.compose.ui.unit.dp
18 | import androidx.core.view.WindowCompat
19 | import com.apkupdater.util.isDark
20 |
21 |
22 | @Composable
23 | fun AppTheme(
24 | darkTheme: Boolean,
25 | dynamicColor: Boolean = true,
26 | content: @Composable () -> Unit
27 | ) {
28 | val colorScheme = when {
29 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
30 | val context = LocalContext.current
31 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
32 | }
33 | darkTheme -> darkColorScheme()
34 | else -> lightColorScheme()
35 | }
36 |
37 | val view = LocalView.current
38 | if (!view.isInEditMode) {
39 | SideEffect {
40 | val activity = view.context as Activity
41 |
42 | // Set Navigation Bar color
43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
44 | activity.window.navigationBarColor = colorScheme.statusBarColor().toArgb()
45 | WindowCompat.getInsetsController(
46 | activity.window,
47 | view
48 | ).isAppearanceLightNavigationBars = !darkTheme
49 | }
50 |
51 | // Set Status Bar color
52 | activity.window.statusBarColor = colorScheme.statusBarColor().toArgb()
53 | WindowCompat.getInsetsController(
54 | activity.window,
55 | view
56 | ).isAppearanceLightStatusBars = !darkTheme
57 | }
58 | }
59 |
60 | MaterialTheme(colorScheme = colorScheme, content = content)
61 | }
62 |
63 | fun ColorScheme.statusBarColor() = surfaceColorAtElevation(3.dp)
64 |
65 | fun isDarkTheme(theme: Int): Boolean {
66 | if (theme == 1) return true
67 | if (theme == 2) return false
68 | return isDark()
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/Badger.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import com.apkupdater.data.ui.Screen
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 |
6 |
7 | class Badger {
8 |
9 | private val badges = MutableStateFlow(mapOf(
10 | Screen.Apps.route to "",
11 | Screen.Search.route to "",
12 | Screen.Updates.route to "",
13 | Screen.Settings.route to ""
14 | ))
15 |
16 | fun flow() = badges
17 |
18 | fun changeSearchBadge(number: String) = changeBadge(Screen.Search.route, number)
19 |
20 | fun changeAppsBadge(number: String) = changeBadge(Screen.Apps.route, number)
21 |
22 | fun changeUpdatesBadge(number: String) = changeBadge(Screen.Updates.route, number)
23 |
24 | private fun changeBadge(route: String, number: String) {
25 | val finalNumber = if (number.toIntOrNull() == 0) "" else number
26 | val newBadges = badges.value.toMutableMap()
27 | newBadges[route] = finalNumber
28 | badges.value = newBadges
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/Clipboard.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import android.content.ClipData
4 | import android.content.Context
5 |
6 |
7 | class Clipboard(private val context: Context) {
8 |
9 | fun copy(text: String, label: String = "Copied Text") {
10 | val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
11 | clipboard.setPrimaryClip(ClipData.newPlainText(label, text))
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/Downloader.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import android.util.Log
4 | import okhttp3.OkHttpClient
5 | import okhttp3.Request
6 | import java.io.File
7 | import java.io.InputStream
8 |
9 |
10 | class Downloader(
11 | private val client: OkHttpClient,
12 | private val apkPureClient: OkHttpClient,
13 | private val auroraClient: OkHttpClient,
14 | private val dir: File
15 | ) {
16 |
17 | fun download(url: String): File {
18 | val file = File(dir, randomUUID())
19 | client.newCall(downloadRequest(url)).execute().use {
20 | if (it.isSuccessful) {
21 | it.body?.byteStream()?.copyTo(file.outputStream())
22 | }
23 | }
24 | return file
25 | }
26 |
27 | fun downloadStream(url: String): InputStream? = runCatching {
28 | val c = when {
29 | url.contains("apkpure") -> apkPureClient
30 | url.contains("aurora") -> auroraClient
31 | else -> client
32 | }
33 | val response = c.newCall(downloadRequest(url)).execute()
34 | if (response.isSuccessful) {
35 | response.body?.let {
36 | return it.byteStream()
37 | }
38 | } else {
39 | response.close()
40 | Log.e("Downloader", "Download failed with error code: ${response.code}")
41 | }
42 | return null
43 | }.getOrElse {
44 | Log.e("Downloader", "Error downloading", it)
45 | null
46 | }
47 |
48 | private fun downloadRequest(url: String) = Request.Builder().url(url).build()
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/InstallLog.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import com.apkupdater.data.ui.AppInstallProgress
4 | import com.apkupdater.data.ui.AppInstallStatus
5 | import kotlinx.coroutines.flow.MutableSharedFlow
6 | import kotlinx.coroutines.flow.asSharedFlow
7 |
8 |
9 | class InstallLog {
10 |
11 | private val status = MutableSharedFlow(100)
12 | private val progress = MutableSharedFlow(100)
13 | private var currentInstallLog: Int = 0
14 |
15 | fun status() = status.asSharedFlow()
16 | fun progress() = progress.asSharedFlow()
17 |
18 | fun cancelCurrentInstall() = status.tryEmit(AppInstallStatus(false, currentInstallLog, false))
19 | fun emitStatus(newStatus: AppInstallStatus) = status.tryEmit(newStatus)
20 | fun emitProgress(newProgress: AppInstallProgress) = progress.tryEmit(newProgress)
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/SnackBar.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import androidx.compose.material3.SnackbarVisuals
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.flow.MutableSharedFlow
7 | import kotlinx.coroutines.flow.SharedFlow
8 | import kotlinx.coroutines.launch
9 |
10 | class SnackBar {
11 |
12 | private val snackBars = MutableSharedFlow()
13 |
14 | fun flow(): SharedFlow = snackBars
15 |
16 | fun snackBar(
17 | scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
18 | message: SnackbarVisuals
19 | ) = scope.launch { snackBars.emit(message) }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/Stringer.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import android.content.Context
4 |
5 | class Stringer(val context: Context) {
6 |
7 | fun get(id: Int) = context.getString(id)
8 | fun get(id: Int, vararg params: Any?) = context.getString(id, *params)
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/Themer.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import com.apkupdater.prefs.Prefs
4 | import com.apkupdater.ui.theme.isDarkTheme
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 |
7 | class Themer(prefs: Prefs) {
8 |
9 | private val theme = MutableStateFlow(isDarkTheme(prefs.theme.get()))
10 |
11 | fun flow() = theme
12 |
13 | fun setTheme(v: Boolean) { theme.value = v }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/UpdatesNotification.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util
2 |
3 | import android.Manifest
4 | import android.annotation.SuppressLint
5 | import android.app.NotificationChannel
6 | import android.app.NotificationManager
7 | import android.app.PendingIntent
8 | import android.content.Context
9 | import android.content.Intent
10 | import android.os.Build
11 | import androidx.activity.compose.ManagedActivityResultLauncher
12 | import androidx.core.app.NotificationCompat
13 | import androidx.core.app.NotificationManagerCompat
14 | import com.apkupdater.R
15 | import com.apkupdater.ui.activity.MainActivity
16 |
17 | class UpdatesNotification(private val context: Context) {
18 |
19 | companion object {
20 | const val UpdateAction = "updateAction"
21 | }
22 |
23 | private val notificationManager get() = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
24 | private val channelId = context.getString(R.string.notification_channel_id)
25 | private val channelName = context.getString(R.string.notification_channel_name)
26 | private val updateTitle = context.getString(R.string.notification_update_title)
27 | private val updateId = 42
28 |
29 | @SuppressLint("MissingPermission")
30 | fun showUpdateNotification(num: Int) {
31 | // Intent for the notification click
32 | val intent = Intent(context, MainActivity::class.java).apply {
33 | flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
34 | action = UpdateAction
35 | }
36 |
37 | val builder = NotificationCompat.Builder(context, channelId)
38 | .setSmallIcon(R.drawable.ic_install)
39 | .setContentTitle(updateTitle)
40 | .setContentText(context.resources.getQuantityString(R.plurals.notification_update_description, num, num))
41 | .setPriority(NotificationCompat.PRIORITY_DEFAULT)
42 | .setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE))
43 | .setAutoCancel(true)
44 |
45 | createNotificationChannel()
46 | if (areNotificationsEnabled()) {
47 | NotificationManagerCompat.from(context).notify(updateId, builder.build())
48 | }
49 | }
50 |
51 | fun checkNotificationPermission(launcher: ManagedActivityResultLauncher) {
52 | if (Build.VERSION.SDK_INT >= 33) {
53 | if (!areNotificationsEnabled()) {
54 | launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
55 | }
56 | }
57 | }
58 |
59 | private fun areNotificationsEnabled() = NotificationManagerCompat
60 | .from(context)
61 | .areNotificationsEnabled()
62 |
63 | private fun createNotificationChannel() {
64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
65 | val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT).apply {
66 | description = context.getString(R.string.notification_channel_description)
67 | }
68 | notificationManager.createNotificationChannel(channel)
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/play/EglExtensionProvider.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util.play
2 |
3 | import android.opengl.GLES10
4 | import android.text.TextUtils
5 | import javax.microedition.khronos.egl.EGL10
6 | import javax.microedition.khronos.egl.EGLConfig
7 | import javax.microedition.khronos.egl.EGLContext
8 | import javax.microedition.khronos.egl.EGLDisplay
9 |
10 |
11 | object EglExtensionProvider {
12 | @JvmStatic
13 | val eglExtensions: List
14 | get() {
15 | val glExtensions: MutableSet = HashSet()
16 | val egl10 = EGLContext.getEGL() as EGL10
17 | val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
18 | egl10.eglInitialize(display, IntArray(2))
19 | val cf = IntArray(1)
20 | if (egl10.eglGetConfigs(display, null, 0, cf)) {
21 | val configs = arrayOfNulls(cf[0])
22 | if (egl10.eglGetConfigs(display, configs, cf[0], cf)) {
23 | val a1 = intArrayOf(EGL10.EGL_WIDTH, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_HEIGHT, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_NONE)
24 | val a2 = intArrayOf(12440, EGL10.EGL_PIXMAP_BIT, EGL10.EGL_NONE)
25 | val a3 = IntArray(1)
26 | for (i in 0 until cf[0]) {
27 | egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, a3)
28 | if (a3[0] != EGL10.EGL_SLOW_CONFIG) {
29 | egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, a3)
30 | if (1 and a3[0] != 0) {
31 | egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, a3)
32 | if (1 and a3[0] != 0) {
33 | addExtensionsForConfig(egl10, display, configs[i], a1, null, glExtensions)
34 | }
35 | if (4 and a3[0] != 0) {
36 | addExtensionsForConfig(egl10, display, configs[i], a1, a2, glExtensions)
37 | }
38 | }
39 | }
40 | }
41 | }
42 | }
43 | egl10.eglTerminate(display)
44 | return ArrayList(glExtensions).sorted()
45 | }
46 |
47 | private fun addExtensionsForConfig(
48 | egl10: EGL10,
49 | eglDisplay: EGLDisplay,
50 | eglConfig: EGLConfig?,
51 | ai: IntArray,
52 | ai1: IntArray?,
53 | set: MutableSet
54 | ) {
55 | val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1)
56 | if (eglContext === EGL10.EGL_NO_CONTEXT) {
57 | return
58 | }
59 | val eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, eglConfig, ai)
60 | if (eglSurface === EGL10.EGL_NO_SURFACE) {
61 | egl10.eglDestroyContext(eglDisplay, eglContext)
62 | } else {
63 | egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
64 | val s = GLES10.glGetString(7939)
65 | if (!TextUtils.isEmpty(s)) {
66 | val split = s.split(" ".toRegex()).toTypedArray()
67 | val i = split.size
68 | set.addAll(listOf(*split).subList(0, i))
69 | }
70 | egl10.eglMakeCurrent(
71 | eglDisplay,
72 | EGL10.EGL_NO_SURFACE,
73 | EGL10.EGL_NO_SURFACE,
74 | EGL10.EGL_NO_CONTEXT
75 | )
76 | egl10.eglDestroySurface(eglDisplay, eglSurface)
77 | egl10.eglDestroyContext(eglDisplay, eglContext)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/play/IProxyHttpClient.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util.play
2 |
3 | import com.aurora.gplayapi.network.IHttpClient
4 |
5 |
6 | interface IProxyHttpClient : IHttpClient {
7 | @Throws(UnsupportedOperationException::class)
8 | fun setProxy(proxyInfo: ProxyInfo): IHttpClient
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/util/play/ProxyInfo.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.util.play
2 |
3 |
4 | data class ProxyInfo(
5 | var protocol: String,
6 | var host: String,
7 | var port: Int,
8 | var proxyUser: String?,
9 | var proxyPassword: String?
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/viewmodel/AppsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.viewmodel
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.apkupdater.data.ui.AppsUiState
7 | import com.apkupdater.prefs.Prefs
8 | import com.apkupdater.repository.AppsRepository
9 | import com.apkupdater.util.Badger
10 | import com.apkupdater.util.launchWithMutex
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.MutableStateFlow
13 | import kotlinx.coroutines.flow.StateFlow
14 | import kotlinx.coroutines.sync.Mutex
15 |
16 | class AppsViewModel(
17 | private val repository: AppsRepository,
18 | private val prefs: Prefs,
19 | private val badger: Badger
20 | ) : ViewModel() {
21 |
22 | private val mutex = Mutex()
23 | private val state = MutableStateFlow(buildLoadingState())
24 |
25 | fun state(): StateFlow = state
26 |
27 | fun refresh(load: Boolean = true) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
28 | if (load) state.value = buildLoadingState()
29 | badger.changeAppsBadge("")
30 | repository.getApps().collect {
31 | it.onSuccess { apps ->
32 | state.value = AppsUiState.Success(
33 | apps,
34 | prefs.excludeSystem.get(),
35 | prefs.excludeStore.get(),
36 | prefs.excludeDisabled.get()
37 | )
38 | badger.changeAppsBadge(apps.size.toString())
39 | }.onFailure { ex ->
40 | state.value = AppsUiState.Error
41 | badger.changeAppsBadge("!")
42 | Log.e("InstalledViewModel", "Error getting apps.", ex)
43 | }
44 | }
45 | }
46 |
47 | fun onSystemClick() = viewModelScope.launchWithMutex(mutex, Dispatchers.Default) {
48 | prefs.excludeSystem.put(!prefs.excludeSystem.get())
49 | refresh(false)
50 | }
51 |
52 | fun onAppStoreClick() = viewModelScope.launchWithMutex(mutex, Dispatchers.Default) {
53 | prefs.excludeStore.put(!prefs.excludeStore.get())
54 | refresh(false)
55 | }
56 |
57 | fun onDisabledClick() = viewModelScope.launchWithMutex(mutex, Dispatchers.Default) {
58 | prefs.excludeDisabled.put(!prefs.excludeDisabled.get())
59 | refresh(false)
60 | }
61 |
62 | fun ignore(packageName: String) = viewModelScope.launchWithMutex(mutex, Dispatchers.Default) {
63 | val ignored = prefs.ignoredApps.get().toMutableList()
64 | if (ignored.contains(packageName)) {
65 | ignored.remove(packageName)
66 | } else {
67 | ignored.add(packageName)
68 | }
69 | prefs.ignoredApps.put(ignored)
70 | refresh(false)
71 | }
72 |
73 | private fun buildLoadingState() = AppsUiState.Loading(
74 | prefs.excludeSystem.get(),
75 | prefs.excludeStore.get(),
76 | prefs.excludeDisabled.get()
77 | )
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/viewmodel/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.viewmodel
2 |
3 | import android.content.Intent
4 | import android.content.pm.PackageInstaller
5 | import android.util.Log
6 | import androidx.activity.compose.ManagedActivityResultLauncher
7 | import androidx.activity.result.ActivityResult
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import androidx.navigation.NavController
11 | import androidx.navigation.NavGraph.Companion.findStartDestination
12 | import com.apkupdater.data.ui.AppInstallStatus
13 | import com.apkupdater.data.ui.Screen
14 | import com.apkupdater.prefs.Prefs
15 | import com.apkupdater.util.InstallLog
16 | import com.apkupdater.util.SessionInstaller
17 | import com.apkupdater.util.UpdatesNotification
18 | import com.apkupdater.util.getAppId
19 | import com.apkupdater.util.getIntentExtra
20 | import com.apkupdater.util.orFalse
21 | import kotlinx.coroutines.Dispatchers
22 | import kotlinx.coroutines.flow.MutableStateFlow
23 | import kotlinx.coroutines.launch
24 |
25 |
26 | class MainViewModel(
27 | private val prefs: Prefs,
28 | private val installLog: InstallLog
29 | ) : ViewModel() {
30 |
31 | val screens = listOf(Screen.Apps, Screen.Search, Screen.Updates, Screen.Settings)
32 |
33 | val isRefreshing = MutableStateFlow(false)
34 |
35 | private var currentInstallId = 0
36 |
37 | fun refresh(
38 | appsViewModel: AppsViewModel,
39 | updatesViewModel: UpdatesViewModel
40 | ) = viewModelScope.launch {
41 | isRefreshing.value = true
42 | appsViewModel.refresh(false)
43 | updatesViewModel.refresh(false).invokeOnCompletion {
44 | isRefreshing.value = false
45 | }
46 | }
47 |
48 | fun processIntent(
49 | intent: Intent,
50 | launcher: ManagedActivityResultLauncher,
51 | updatesViewModel: UpdatesViewModel,
52 | navController: NavController
53 | ) {
54 | when {
55 | intent.action == UpdatesNotification.UpdateAction -> processUpdateIntent(navController, updatesViewModel)
56 | intent.action?.contains(SessionInstaller.INSTALL_ACTION).orFalse() -> processInstallIntent(intent, launcher)
57 | else -> {}
58 | }
59 | }
60 |
61 | fun navigateTo(navController: NavController, route: String) = navController.navigate(route) {
62 | popUpTo(navController.graph.findStartDestination().id) { saveState = true }
63 | launchSingleTop = true
64 | restoreState = true
65 | prefs.lastTab.put(route)
66 | }
67 |
68 | fun getLastRoute() = prefs.lastTab.get()
69 |
70 | private fun processInstallIntent(
71 | intent: Intent,
72 | launcher: ManagedActivityResultLauncher
73 | ) = viewModelScope.launch(Dispatchers.IO) {
74 | when (intent.extras?.getInt(PackageInstaller.EXTRA_STATUS)) {
75 | PackageInstaller.STATUS_PENDING_USER_ACTION -> {
76 | currentInstallId = intent.getAppId() ?: 0
77 | // Launch intent to confirm install
78 | intent.getIntentExtra()?.let {
79 | it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
80 | launcher.launch(it)
81 | }
82 | }
83 | PackageInstaller.STATUS_SUCCESS -> {
84 | intent.getAppId()?.let {
85 | installLog.emitStatus(AppInstallStatus(true, it))
86 | }
87 | }
88 | else -> {
89 | // We assume error and cancel the install
90 | intent.getAppId()?.let {
91 | installLog.emitStatus(AppInstallStatus(false, it))
92 | }
93 | val message = intent.extras?.getString(PackageInstaller.EXTRA_STATUS_MESSAGE)
94 | Log.e("MainViewModel", "Failed to install app: $message $intent")
95 | }
96 | }
97 | }
98 |
99 | private fun processUpdateIntent(
100 | navController: NavController,
101 | updatesViewModel: UpdatesViewModel
102 | ) {
103 | navigateTo(navController, Screen.Updates.route)
104 | updatesViewModel.refresh()
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/viewmodel/SearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.viewmodel
2 |
3 | import androidx.lifecycle.viewModelScope
4 | import com.apkupdater.data.ui.AppUpdate
5 | import com.apkupdater.data.ui.SearchUiState
6 | import com.apkupdater.data.ui.removeId
7 | import com.apkupdater.data.ui.setIsInstalling
8 | import com.apkupdater.data.ui.setProgress
9 | import com.apkupdater.prefs.Prefs
10 | import com.apkupdater.repository.SearchRepository
11 | import com.apkupdater.util.Badger
12 | import com.apkupdater.util.Downloader
13 | import com.apkupdater.util.InstallLog
14 | import com.apkupdater.util.SessionInstaller
15 | import com.apkupdater.util.SnackBar
16 | import com.apkupdater.util.Stringer
17 | import com.apkupdater.util.launchWithMutex
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.Job
20 | import kotlinx.coroutines.flow.MutableStateFlow
21 | import kotlinx.coroutines.flow.StateFlow
22 | import kotlinx.coroutines.launch
23 | import kotlinx.coroutines.sync.Mutex
24 |
25 |
26 | class SearchViewModel(
27 | private val searchRepository: SearchRepository,
28 | private val installer: SessionInstaller,
29 | private val badger: Badger,
30 | downloader: Downloader,
31 | prefs: Prefs,
32 | snackBar: SnackBar,
33 | stringer: Stringer,
34 | installLog: InstallLog
35 | ) : InstallViewModel(downloader, installer, prefs, snackBar, stringer, installLog) {
36 |
37 | private val mutex = Mutex()
38 | private val state = MutableStateFlow(SearchUiState.Success(emptyList()))
39 | private var job: Job? = null
40 |
41 | init {
42 | subscribeToInstallStatus(state.value.updates())
43 | subscribeToInstallProgress { progress ->
44 | state.value = SearchUiState.Success(state.value.mutableUpdates().setProgress(progress))
45 | }
46 | }
47 |
48 | fun state(): StateFlow = state
49 |
50 | fun search(text: String) {
51 | job?.cancel()
52 | job = searchJob(text)
53 | }
54 |
55 | private fun searchJob(text: String) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
56 | state.value = SearchUiState.Loading
57 | badger.changeSearchBadge("")
58 | searchRepository.search(text).collect {
59 | it.onSuccess { apps ->
60 | state.value = SearchUiState.Success(apps)
61 | badger.changeSearchBadge(apps.size.toString())
62 | }.onFailure {
63 | badger.changeSearchBadge("!")
64 | state.value = SearchUiState.Error
65 | }
66 | }
67 | }
68 |
69 | override fun cancelInstall(id: Int) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
70 | state.value = SearchUiState.Success(state.value.mutableUpdates().setIsInstalling(id, false))
71 | installer.finish()
72 | }
73 |
74 | override fun finishInstall(id: Int) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
75 | val updates = state.value.mutableUpdates().removeId(id)
76 | state.value = SearchUiState.Success(updates)
77 | badger.changeSearchBadge(updates.size.toString())
78 | installer.finish()
79 | }
80 |
81 | override fun downloadAndRootInstall(update: AppUpdate) = viewModelScope.launch(Dispatchers.IO) {
82 | state.value = SearchUiState.Success(state.value.mutableUpdates().setIsInstalling(update.id, true))
83 | downloadAndRootInstall(update.id, update.link)
84 | }
85 |
86 | override fun downloadAndInstall(update: AppUpdate) = viewModelScope.launch(Dispatchers.IO) {
87 | if(installer.checkPermission()) {
88 | state.value = SearchUiState.Success(state.value.mutableUpdates().setIsInstalling(update.id, true))
89 | downloadAndInstall(update.id, update.packageName, update.link)
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/viewmodel/UpdatesViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.viewmodel
2 |
3 | import androidx.lifecycle.viewModelScope
4 | import com.apkupdater.data.ui.AppUpdate
5 | import com.apkupdater.data.ui.UpdatesUiState
6 | import com.apkupdater.data.ui.removeId
7 | import com.apkupdater.data.ui.setIsInstalling
8 | import com.apkupdater.data.ui.setProgress
9 | import com.apkupdater.prefs.Prefs
10 | import com.apkupdater.repository.UpdatesRepository
11 | import com.apkupdater.util.Badger
12 | import com.apkupdater.util.Downloader
13 | import com.apkupdater.util.InstallLog
14 | import com.apkupdater.util.SessionInstaller
15 | import com.apkupdater.util.SnackBar
16 | import com.apkupdater.util.Stringer
17 | import com.apkupdater.util.launchWithMutex
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.flow.MutableStateFlow
20 | import kotlinx.coroutines.flow.StateFlow
21 | import kotlinx.coroutines.launch
22 | import kotlinx.coroutines.sync.Mutex
23 |
24 |
25 | class UpdatesViewModel(
26 | private val updatesRepository: UpdatesRepository,
27 | private val installer: SessionInstaller,
28 | private val prefs: Prefs,
29 | private val badger: Badger,
30 | downloader: Downloader,
31 | snackBar: SnackBar,
32 | stringer: Stringer,
33 | installLog: InstallLog
34 | ) : InstallViewModel(downloader, installer, prefs, snackBar, stringer, installLog) {
35 |
36 | private val mutex = Mutex()
37 | private val state = MutableStateFlow(UpdatesUiState.Loading)
38 |
39 | init {
40 | subscribeToInstallStatus(state.value.updates())
41 | subscribeToInstallProgress { progress ->
42 | state.value = UpdatesUiState.Success(state.value.mutableUpdates().setProgress(progress))
43 | }
44 | }
45 |
46 | fun state(): StateFlow = state
47 |
48 | fun refresh(load: Boolean = true) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
49 | if (load) state.value = UpdatesUiState.Loading
50 | badger.changeUpdatesBadge("")
51 | updatesRepository.updates().collect {
52 | setSuccess(it)
53 | }
54 | }
55 |
56 | fun ignoreVersion(id: Int) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
57 | val ignored = prefs.ignoredVersions.get().toMutableList()
58 | if (ignored.contains(id)) ignored.remove(id) else ignored.add(id)
59 | prefs.ignoredVersions.put(ignored)
60 | setSuccess(state.value.mutableUpdates())
61 | }
62 |
63 | override fun cancelInstall(id: Int) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
64 | state.value = UpdatesUiState.Success(state.value.mutableUpdates().setIsInstalling(id, false))
65 | installer.finish()
66 | }
67 |
68 | override fun finishInstall(id: Int) = viewModelScope.launchWithMutex(mutex, Dispatchers.IO) {
69 | setSuccess(state.value.mutableUpdates().removeId(id))
70 | installer.finish()
71 | }
72 |
73 | override fun downloadAndRootInstall(update: AppUpdate) = viewModelScope.launch(Dispatchers.IO) {
74 | state.value = UpdatesUiState.Success(state.value.mutableUpdates().setIsInstalling(update.id, true))
75 | downloadAndRootInstall(update.id, update.link)
76 | }
77 |
78 | override fun downloadAndInstall(update: AppUpdate) = viewModelScope.launch(Dispatchers.IO) {
79 | if(installer.checkPermission()) {
80 | state.value = UpdatesUiState.Success(state.value.mutableUpdates().setIsInstalling(update.id, true))
81 | downloadAndInstall(update.id, update.packageName, update.link)
82 | }
83 | }
84 |
85 | private fun List.filterIgnoredVersions(ignoredVersions: List) = this
86 | .filter { !ignoredVersions.contains(it.id) }
87 |
88 | private fun setSuccess(updates: List) = updates
89 | .filterIgnoredVersions(prefs.ignoredVersions.get())
90 | .let {
91 | state.value = UpdatesUiState.Success(it)
92 | badger.changeUpdatesBadge(it.size.toString())
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/apkupdater/worker/UpdatesWorker.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater.worker
2 |
3 | import android.content.Context
4 | import androidx.work.CoroutineWorker
5 | import androidx.work.ExistingPeriodicWorkPolicy
6 | import androidx.work.PeriodicWorkRequestBuilder
7 | import androidx.work.WorkManager
8 | import androidx.work.WorkerParameters
9 | import com.apkupdater.prefs.Prefs
10 | import com.apkupdater.repository.UpdatesRepository
11 | import com.apkupdater.util.UpdatesNotification
12 | import com.apkupdater.util.millisUntilHour
13 | import org.koin.core.component.KoinComponent
14 | import org.koin.core.component.inject
15 | import java.util.concurrent.TimeUnit
16 | import kotlin.random.Random
17 |
18 |
19 | class UpdatesWorker(
20 | context: Context,
21 | workerParams: WorkerParameters
22 | ): CoroutineWorker(context, workerParams), KoinComponent {
23 |
24 | companion object: KoinComponent {
25 | private const val TAG = "UpdatesWorker"
26 | private val prefs: Prefs by inject()
27 |
28 | fun cancel(workManager: WorkManager) = workManager.cancelUniqueWork(TAG)
29 |
30 | fun launch(workManager: WorkManager) {
31 | val request = PeriodicWorkRequestBuilder(getDays(), TimeUnit.DAYS)
32 | .setInitialDelay(
33 | millisUntilHour(prefs.alarmHour.get()) + randomDelay(),
34 | TimeUnit.MILLISECONDS
35 | ).build()
36 | workManager.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
37 | }
38 |
39 | private fun randomDelay() = if (prefs.useApkMirror.get())
40 | Random.nextLong(0, 59 * 60 * 1_000)
41 | else
42 | Random.nextLong(-5 * 60 * 1_000, 5 * 60 * 1_000)
43 |
44 | private fun getDays() = when(prefs.alarmFrequency.get()) {
45 | 0 -> 1L
46 | 1 -> 3L
47 | 2 -> 7L
48 | else -> 1L
49 | }
50 | }
51 |
52 | private val updatesRepository: UpdatesRepository by inject()
53 | private val notification: UpdatesNotification by inject()
54 |
55 | override suspend fun doWork(): Result {
56 | updatesRepository.updates().collect {
57 | if (it.isNotEmpty()) {
58 | notification.showUpdateNotification(it.size)
59 | }
60 | }
61 | return Result.success()
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/drawable/banner.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_alarm.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_alpha.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_androidtv.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_animation.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apkmirror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/drawable/ic_apkmirror.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apkpure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/drawable/ic_apkpure.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_appstore.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_appstore_off.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_aptoide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/drawable/ic_aptoide.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_beta.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_copy.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_disabled.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_disabled_off.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_empty.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_frequency.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gitlab.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_hour.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_install.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_izzy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/drawable/ic_izzy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_landscape.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_portrait.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pre_release.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_root.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_safe.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_system.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_system_off.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_theme.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visible.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visible_off.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-alb/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Aplikacionet
5 | Kërko
6 | Përditësime
7 | Cilësimet
8 | "Diçka shkoi keq\n\uD83E\uDD26"
9 | Ignoro Versionin
10 | Ignoro Aplikacionin
11 | Mos e Ignoro Aplikacionin
12 | Kërko për Përditësime
13 | Përjashto Aplikacionet e Sistemit
14 | Përfshij Aplikacionet e Sistemit
15 | Përjashto Dyqanin e Aplikacioneve
16 | Përfshij Dyqanin e Aplikacioneve
17 | Përjashto Aplikacionet e Çaktivizuara
18 | Përfshij Aplikacionet e Çaktivizuara
19 | Instalo Aplikacionin
20 | Ikona e Aplikacionit
21 | %1$s u instalua.
22 | %1$s dështoi të instalohet.
23 |
24 | // Settings
25 | Kolonat në Portret
26 | Kolonat në Peizazh
27 | Burimet
28 | UI
29 | Alarmi
30 | Opsionet
31 | Utilitetet
32 | Ora e Alarmit
33 | Çdo Ditë
34 | Çdo 3 Ditë
35 | Çdo Javë
36 | UI i Android TV
37 | Ignoro Alpha
38 | Ignoro Beta
39 | Ignoro Para-lirim
40 | Përdor Dyqane të Sigurta (Aptoide)
41 | Instalim me Root
42 | ApkMirror
43 | F-Droid Kryesor
44 | F-Droid Izzy
45 | Aptoide
46 | GitHub
47 | GitLab
48 | APKPure
49 | Rreth
50 | Frekuenca
51 | Tema
52 | Sistemi
53 | Errët
54 | Dritë
55 | Merr kodin burim, raporto gabimet, kërko karakteristika të reja dhe shto përkthimet.
56 | Nëse ju pëlqen aplikacioni, mendoni për dhurimin për një shkak të vlefshëm.
57 | Kopjo në të kujtesë
58 | Kopjo Listën e Aplikacioneve
59 |
60 | // Notifications
61 | Përditësime
62 | Kanali për njoftimet e përditësimeve.
63 | updateChannel
64 | Përditësime
65 |
66 | - Asnjë përditësim i gjetur.
67 | - Gjeti %1$d përditësim.
68 | - Gjeti %1$d përditësime.
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Apps
5 | Suche
6 | Updates
7 | Setup
8 | "Etwas ist schiefgelaufen\n\uD83E\uDD26"
9 | Ausblenden
10 | Ignorieren
11 | Überprüfen
12 | Suche nach Updates
13 | System-Apps ignorieren
14 | System-Apps überprüfen
15 | App-Store ignorieren
16 | App-Store überprüfen
17 | Deaktivierte Apps ignorieren
18 | Deaktivierte Apps überprüfen
19 | Installieren
20 | App-Icon
21 | %1$s installiert.
22 | %1$s wurde nicht installiert.
23 | Root-Installation dieser App wird nicht unterstützt.
24 |
25 | // Settings
26 | Anzahl der Spalten
27 | Anzahl der Zeilen
28 | Textanimation verwenden
29 | Quellen
30 | Benutzeroberfläche
31 | Alarm
32 | Optionen
33 | Hilfsmittel
34 | Alarmzeit
35 | Täglich
36 | 3-Tägig
37 | Wöchentlich
38 | Alphas ignorieren
39 | Betas ignorieren
40 | Pre-Releases ignorieren
41 | Sichere Stores nutzen (Aptoide)
42 | Root-Installation
43 | Über
44 | Häufigkeit
45 | Thema
46 | System
47 | Dunkel
48 | Hell
49 | Hol dir den Quellcode, melde Fehler und schlage Funktionen / Übersetzungen vor.
50 | Wenn dir die App gefällt solltest du hier eine Spende in Betracht ziehen.
51 | Kopiere in Zwischenablage
52 | App-Liste kopieren
53 | App-Logs kopieren
54 |
55 | // Notifications
56 | Updates
57 | Kanal für Update-Benachrichtigungen.
58 | Updates
59 |
60 | - Keine Updates gefunden.
61 | - %1$d Update gefunden.
62 | - %1$d Updates gefunden.
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Aplicaciones
5 | Búsqueda
6 | Actualizaciones
7 | Configuración
8 | Algo ha fallado\\n\\uD83E\\uDD26
9 | Ignorar versión
10 | Ignorar Apliación
11 | No Ignorar Aplicación
12 | Buscar Actualizaciones
13 | Excluir aplicaciones del sistema
14 | Incluir aplicaciones del sistema
15 | Excluir aplicaciones de App Stores
16 | Incluir aplicaciones de App Stores
17 | Excluir aplicaciones deshabilitadas
18 | Incluir aplicaciones deshabilitadas
19 | Instalar Aplicación
20 | Icono de la Applicación
21 | %1$s instalado.
22 | %1$s fallo en la instalación.
23 |
24 | // Settings
25 | Columnas en modo Retrato
26 | Columnas en modo Apaisado
27 | Fuentes
28 | Interfaz de Usuario
29 | Alarma
30 | Opciones
31 | Hora de Alarma
32 | Diaria
33 | A cada 3 días
34 | Semanal
35 | Interfaz de Android TV
36 | Ignorar Alpha
37 | Ignorar Beta
38 | Ignorar Prelanzamiento
39 | Utilizar tiendas seguras (Aptoide)
40 | Instalación root
41 | ApkMirror
42 | F-Droid Main
43 | F-Droid Izzy
44 | Aptoide
45 | GitHub
46 | APKPure (Beta)
47 | Sobre
48 | Frecuencia
49 | Tema
50 | Sistema
51 | Oscura
52 | Claro
53 | Obtenga el código fuente, informe errores, solicite nuevas funciones y agregue traducciones.
54 | Si te gusta la aplicación, considera hacer una donación a una buena causa.
55 | Copiar a la portapapeles
56 | Copiar lista de aplicaciones
57 |
58 | // Notifications
59 | Actualizaciones
60 | Canal para las notificaciones de las actualizaciones.
61 | updateChannel
62 | Actualizaciones
63 |
64 | - No se encontraron actualizaciones.
65 | - Se encontró %1$d actualización.
66 | - Se encontraron %1$d actualizaciones.
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/values-he/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | ישומים
5 | חיפוש
6 | עדכונים
7 | הגדרות
8 | "קרתה שגיאה\n\uD83E\uDD26"
9 | התעלם מגירסה
10 | התעלמות מישומון
11 | בטל התעלמות מישומון
12 | חפש עדכונים
13 | החרג ישומוני מערכת
14 | כלול ישומוני מערכת
15 | החרג ישומוני חנות
16 | כלול ישומוני חנות
17 | החרג ישומונים מבוטלים
18 | כלול ישומונים מבוטלים
19 | התקן ישומון
20 | צלמית ישומון
21 | %1$s ההתקנה הצליחה.
22 | %1$s ההתקנה נכשלה.
23 |
24 | // Settings
25 | עמודות לרוחב
26 | עמודות לאורך
27 | מקורות
28 | ממשק משתמש
29 | התרעה
30 | אפשרויות
31 | כלים
32 | שעת התרעה
33 | יומי
34 | תלת-יומי
35 | שבועי
36 | Android TV ממשק משתמש עבור
37 | התעלם מאלפא
38 | התעלם מביטא
39 | התעלם משחרור מוקדם
40 | השתמש בחנות בטוחה (Aptoide)
41 | התקנת שורש
42 | ApkMirror
43 | F-Droid Main
44 | F-Droid Izzy
45 | Aptoide
46 | GitHub
47 | GitLab
48 | APKPure
49 | אודות
50 | תדירות
51 | ערכת נושא
52 | מערכת
53 | כהה
54 | בהיר
55 | לקוד מקור, דיווח שגיאות, בקשה לתכוניות חדשות ותרגומים.
56 | אם אהבת את הישומון, שקול לתרום למטרה הנעלה.
57 | העתק לזיכרון
58 | העתק רשימת ישומונים
59 |
60 | // Notifications
61 | עדכונים
62 | ערוץ עבור הודעות עדכונים.
63 | עדכן ערוץ
64 | עדכונים
65 |
66 | - לא נמצאו עדכונים.
67 | - נמצאו %1$d עדכונים.
68 | - נמצאו %1$d עדכונים.
69 | - נמצאו %1$d עדכונים.
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Applicazioni
5 | Cerca
6 | Aggiornamenti
7 | Impostazioni
8 | Qualcosa è andato storto\n\uD83E\uDD26
9 | Salta questa versione
10 | Ignora applicazioni
11 | Includi applicazioni
12 | Cerca aggiornamenti
13 | Escludi applicazioni di sistema
14 | Includi applicazioni di sistema
15 | Escludi applicazioni store
16 | Includi applicazioni store
17 | Escludi applicazioni disattivate
18 | Includi applicazioni disattivate
19 | Installa applicazioni
20 | Icona dell\'applicazione
21 | %1$s installate.
22 | %1$s installazioni fallite.
23 |
24 | // Settings
25 | Colonne verticali
26 | Colonne orizzontali
27 | Fonti
28 | Interfaccia grafica
29 | Notifiche
30 | Opzioni
31 | Utilità
32 | Orario di notifica
33 | Giornaliera
34 | Ogni 3 giorni
35 | Settimanale
36 | Interfaccia grafica del Android TV
37 | Ignora Alpha
38 | Ignora Beta
39 | Ignora pre-rilascio
40 | Utilizzare store sicuri (Aptoide)
41 | Installa con root
42 | ApkMirror
43 | F-Droid Main
44 | F-Droid Izzy
45 | Aptoide
46 | GitHub
47 | APKPure (Beta)
48 | Informazioni
49 | Frequenza
50 | Tema
51 | Sistema
52 | Scuro
53 | Chiaro
54 | Ottieni il codice sorgente, segnala bug, richiedi nuove funzionalità e aggiungi traduzioni.
55 | Se ti piace l\'applicazione, considera l\'idea di fare una donazione per una buona causa.
56 | Copia negli appunti
57 | Copia lista app
58 |
59 | // Notifications
60 | Aggiornamenti
61 | Canale per notifiche di aggiornamento.
62 | updateChannel
63 | Aggiornamenti
64 |
65 | - Nessun aggiornamento trovato.
66 | - Trovato %1$d aggiornamento.
67 | - Trovato %1$d aggiornamenti.
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values-jp/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | アプリ
5 | 検索
6 | アップデート
7 | 設定
8 | "失敗しました\n\uD83E\uDD26"
9 | バージョンを無視する
10 | アプリを除外する
11 | アプリの無視を解除する
12 | アップデート一覧を更新する
13 | システムアプリを除外する
14 | システムアプリをリストに含める
15 | アプリストアを除外する
16 | アプリストアをリストに含める
17 | 無効にしたアプリを除外する
18 | 無効にしたアプリをリストに含める
19 | アプリをインストールする
20 | アプリアイコン
21 | %1$sをインストールしました。
22 | %1$sのインストールに失敗しました。
23 | このアプリのrootインストールはサポートされていません。
24 |
25 | // Settings
26 | ポートレートモードでの列数
27 | ランドスケープモードでの列数
28 | テキストのアニメーションを再生する
29 | ソース
30 | UI
31 | アラーム
32 | オプション
33 | ユーティリティ
34 | アラームを鳴らす時刻
35 | 毎日
36 | 3日ごと
37 | 毎週
38 | Android TVのUI
39 | アルファ版を無視する
40 | ベータ版を無視する
41 | プレリリースを無視する
42 | 安全なストアを使用する(Aptoide)
43 | rootインストール
44 | ApkMirror
45 | F-Droid Main
46 | F-Droid Izzy
47 | Aptoide
48 | GitHub
49 | GitLab
50 | APKPure
51 | Play
52 | このアプリについて
53 | 頻度
54 | テーマ
55 | システムに合わせる
56 | ダークテーマ
57 | ライトテーマ
58 | ソースコードの取得、バグの報告、新機能のリクエスト、翻訳の追加ができます。
59 | このアプリが気に入ったら、寄付をお願いします。
60 | クリップボードにコピー
61 | アプリ一覧をコピー
62 | アプリのログをコピー
63 |
64 | // Notifications
65 | Updates
66 | アップデートの通知を受け取るチャンネルです。
67 | アップデートチャンネル
68 | アップデート
69 |
70 | - アップデートはありません。
71 | - %1$d件のアップデートがあります。
72 | - %1$d件のアップデートがあります。
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | 모든 앱
5 | 검색
6 | 업데이트
7 | 설정
8 | "문제가 발생했습니다.\n\uD83E\uDD26"
9 | 버전 무시
10 | 앱 무시
11 | 앱 무시 해제
12 | 업데이트 찾기
13 | 시스템 앱 제외
14 | 시스템 앱 포함
15 | 앱스토어 제외
16 | 앱스토어 포함
17 | 비활성화된 앱 제외
18 | 비활성화된 앱 포함
19 | 앱 설치
20 | 앱 아이콘
21 | %1$s 설치됨.
22 | %1$s 설치 실패됨.
23 |
24 | // Settings
25 | 세로 열
26 | 가로 열
27 | 텍스트 애니메이션 재생
28 | 출처
29 | UI
30 | 알람
31 | 옵션
32 | 유틸리티
33 | 알람 시간
34 | 일일
35 | 3일
36 | 주간
37 | 안드로이드TV UI
38 | 알파 무시
39 | 베타 무시
40 | 시험판 무시
41 | 안전한 상점 이용(Aptoide)
42 | 루트 설치
43 | ApkMirror
44 | F-Droid Main
45 | F-Droid Izzy
46 | Aptoide
47 | GitHub
48 | GitLab
49 | APKPure
50 | 도움말
51 | 빈도
52 | 테마
53 | 시스템
54 | 어둡게
55 | 밝게
56 | 소스 코드, 버그 보고, 새로운 기능 요청, 번역 추가 해주세요.
57 | 앱이 마음에 든다면 기부 해주세요 :) .
58 | 클립보드에 복사
59 | 앱 목록 복사
60 |
61 | // Notifications
62 | 업데이트
63 | 업데이트 알림 채널.
64 | 업데이트 채널
65 | 업데이트
66 |
67 | - 업데이트를 찾을 수 없어요.
68 | - %1$d 업데이트를 찾았어요.
69 | - %1$d개의 업데이트를 찾았어요.
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/values-my-rMM/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Appများ
5 | ရှာဖွေရန်
6 | ဗားရှင်းအသစ်များ
7 | ဆက်တင်များ
8 | "တစ်ခုခုမှားယွင်းနေပါသည်\n\uD83E\uDD26"
9 | ငြင်းပယ်ထားသောAppများ
10 | မငြင်းပယ်ထားသောAppများ
11 | ဗားရှင်းအသစ်များရှာဖွေခြင်း
12 | System Appsများမပါဝင်
13 | System Appsများပါအပါဝင်
14 | App Storeမပါဝင်
15 | App Storeအပါအဝင်
16 | အသုံးပြုမရသော Apps များမပါဝင်
17 | အသုံးပြုမရသော Apps များပါအပါအဝင်
18 | ထည့်သွင်းထားသောအပ်ပလီကေးရှင်း
19 | အပ်ပလီကေးရှင်းရုပ်ပုံ
20 | %1$s ထည့်သွင်းပြီး။
21 | %1$s ထည့်သွင်းမှုမအောင်မြင်ပါ။
22 |
23 | // Settings
24 | အလျားလိုက်အကန့်
25 | ဒေါင်လိုက်အကန့်
26 | အရင်းမြစ်များ
27 | ပြသပုံ
28 | နှိုးစက်အသိပေးချက်
29 | ရွေးချယ်စရာများ
30 | အခြားလုပ်ဆောင်ချက်များ
31 | သတိပေးချိန် နာရီ
32 | နေ့စဥ်
33 | သုံးရက်တိုင်း
34 | အပတ်စဥ်
35 | Android TV UI
36 | အယ်ဖာ(အစမ်း)များမပါဝင်
37 | ဘယ်တာ(အကြို)များမပါဝင်
38 | ကြိုတင်စမ်းသပ်Appများမပါဝင်
39 | လုံခြုံမှု(Aptoide)စတိုးအသုံးပြုရန်
40 | Rootချပြီးထည့်သွင်းမှု
41 | ApkMirror
42 | F-Droid Main
43 | F-Droid Izzy
44 | Aptoide
45 | GitHub
46 | APKPure (Beta)
47 | ကျွန်ုပ်တို့အကြောင်း
48 | ကြိမ်ရေ
49 | တင်ပြပုံ
50 | စက်အတိုင်း
51 | အမှောင်
52 | အလင်း
53 | ပင်ရင်းကုဒ်တွင် ဘာသာစကားများ၊ လိုအပ်ချက်များ၊ ပါဝင်မှုအသစ်များ ထည့်သွင်းရန်။
54 | ယခုအပ်ပလီကေးရှင်းကိုနှစ်သက်ပါက ကူညီလှူတန်းရန်။
55 | ကော်ပီကူးယူသည်
56 | Appလိပ်စာကိုကူးယူသည်
57 |
58 | // Notifications
59 | ဗားရှင်းအသစ်များ
60 | ဗားရှင်းအသစ်များအတွက်အသိပေးချက်ရယူရန်။
61 | updateChannel
62 | ဗားရှင်းအသစ်များ
63 |
64 | - ဗားရှင်းအသစ် မတွေ့ပါ။
65 | - ဗားရှင်းအသစ် %1$d ခု။
66 | - ဗားရှင်းအသစ် %1$d ခု။
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Applicaties
4 | Zoeken
5 | Updates
6 | Instellingen
7 | Er is iets misgegaan\\n\\uD83E\\uDD26
8 | App Negeren
9 | App niet Negeren
10 | Zoeken naar Updates
11 | Systeemapps Uitsluiten
12 | Systeemapps Toestaan
13 | App Store Uitsluiten
14 | App Store Toestaan
15 | Uitgeschakelde Apps Uitsluiten
16 | Uitgeschakelde Apps Toestaan
17 | App Installeren
18 | App Icoon
19 | %1$s geïnstalleerd.
20 | %1$s niet geïnstalleerd.
21 |
22 | // Settings
23 | Staande Kolommen
24 | Liggende Kolommen
25 | Bronnen
26 | UI
27 | Alarm
28 | Opties
29 | Hulpmiddelen
30 | Alarm In Uren
31 | Dagelijks
32 | 3-Daags
33 | Wekelijks
34 | Alpha Negeren
35 | Bèta Negeren
36 | Pre-release negeren
37 | Gebruik veilige App Stores (Aptoide)
38 | Root Installatie
39 | Over
40 | Frequentie
41 | Thema
42 | Systeem
43 | Donker
44 | Licht
45 | Verkrijg de broncode, rapporteer bugs, vraag nieuwe functies aan en voeg vertalingen toe.
46 | Als je de app leuk vindt, overweeg dan om te doneren aan een goed doel.
47 | Kopieer naar klembord
48 | Kopieer applicatielijst
49 |
50 | // Notifications
51 | Updates
52 | Kanaal voor update notificaties
53 | Updates
54 |
55 | - %1$d Update gevonden.
56 | - %1$d Updates gevonden.
57 | - %1$d Updates gevonden.
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | "Aplicações"
5 | "Procurar"
6 | "Atualizações"
7 | "Configurações"
8 | "Aconteceu algo de errado\n\uD83E\uDD26"
9 | Ignorar a versão
10 | "Ignorar a aplicação"
11 | "Não ignorar a aplicação"
12 | "Procurar atualizações"
13 | "Excluir a Loja de Aplicações"
14 | "Incluir aplicações do sistema"
15 | "Excluir a Loja de Aplicações"
16 | "Incluir a Loja de Aplicações"
17 | "Excluir aplicações desativadas"
18 | "Incluir aplicações desativadas"
19 | "Instalar aplicação"
20 | "Ícone da aplicação"
21 | "%1$s foi instalado com sucesso."
22 | A instalação de "%1$s falhou."
23 |
24 | // Settings
25 | "Colunas em modo retrato"
26 | "Colunas em modo paisagem"
27 | "Fontes"
28 | "Interface"
29 | "Alarme"
30 | "Opções"
31 | "Intervalo de horas"
32 | "Diário"
33 | "A cada 3 dias"
34 | "Semanalmente"
35 | Interface do Android TV
36 | "Ignorar alpha"
37 | "Ignorar beta"
38 | "Ignorar pré-lançamento"
39 | "Utilizar lojas seguras (Aptoide)"
40 | "Instalação root"
41 | ApkMirror
42 | F-Droid (Principal)
43 | F-Droid (Izzy)
44 | Aptoide
45 | GitHub
46 | APKPure (Beta)
47 | "Sobre"
48 | "Frequência"
49 | "Tema"
50 | "Sistema"
51 | "Escuro"
52 | "Claro"
53 | "Obter código fonte, reportar bugs, requisitar novas funcionalidades e traduções."
54 | "Se gostas da app, considera fazer um donativo para uma causa justa."
55 | Copiar para a área de transferência
56 | Copiar lista da app
57 |
58 | // Notifications
59 | Atualizações
60 | Canal para notificações de atualizações.
61 | updateChannel
62 | Atualizações
63 |
64 | - Nenhuma atualização encontrada.
65 | - Foi encontrada %1$d atualização.
66 | - Foram encontradas %1$d atualizações.
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ro/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Aplicații
5 | Caută
6 | Actualizări
7 | Setări
8 | "Ceva nu a mers bine\n\uD83E\uDD26"
9 | Ignoră Aplicația
10 | Nu mai ignora Aplicația
11 | Caută actualizări
12 | Exclude Aplicațiile de Sistem
13 | Include Aplicațiile de Sistem
14 | Exclude Magazinul de Aplicații
15 | Include Magazinul de Aplicații
16 | Exclude Aplicațiile Dezactivate
17 | Include Aplicațiile Dezactivate
18 | Instalează Aplicația
19 | Iconița Aplicației
20 |
21 | // Settings
22 | Coloane Portret
23 | Coloane Peisaj
24 | Surse
25 | UI
26 | Alarmă
27 | Opțiuni
28 | Ora Alarmei
29 | Zilnic
30 | În fiecare 3 zile
31 | Săptămânal
32 | Android TV UI
33 | Ignoră Aplicațiile Alpha
34 | Ignoră Aplicațiile Beta
35 | Instalează Automat Folosind ROOT
36 | ApkMirror
37 | F-Droid
38 | Aptoide
39 | GitHub
40 | Despre
41 | Frecvență
42 |
43 | // Notifications
44 | Actualizări
45 | Canalul pentru notificări despre actualizări.
46 | updateChannel
47 | Actualizări
48 |
49 | - Au fost găsite %1$d actualizări.
50 | - Nicio actualizare găsită.
51 | - A fost găsită %1$d actualizare.
52 | - Au fost găsite %1$d actualizări.
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | APKUpdater
3 | Uygulamalar
4 | Ara
5 | Güncellemeler
6 | Ayarlar
7 | "Bir şeyler ters gitti\n\uD83E\uDD26"
8 | Sürümü Yoksay
9 | Uygulamayı Yoksay
10 | Uygulamayı Yoksayılanlardan Çıkar
11 | Güncellemeleri Denetle
12 | Sistem Uygulamalarını Hariç Tut
13 | Sistem Uygulamalarını Dahil Et
14 | App Storeyi hariç tut
15 | App Storeyi dahil et
16 | Devre Dışı Uygulamaları Hariç Tut
17 | Devre Dışı Uygulamaları Dahil Et
18 | Uygulamayı Yükle
19 | Uygulama Simgesi
20 | %1$s yüklendi.
21 | %1$s yüklenemedi.
22 | Dikey Sütunlar
23 | Yatay Sütunlar
24 | Metin Animasyonlarını Oynat
25 | Kaynaklar
26 | UI
27 | Alarm
28 | Seçenekler
29 | ARAÇLAR
30 | Alarm Saati
31 | Günlük
32 | 3 Gün
33 | Haftalık
34 | Android TV Kullanıcı Arayüzü
35 | Alfayı yoksay
36 | Betayı Yoksay
37 | Ön sürümü yoksay
38 | Güvenli Mağazaları Kullan (Aptoide)
39 | Root Kurulumu
40 | ApkMirror
41 | F-Droid (Ana)
42 | F-Droid (Izzy)
43 | Aptoide
44 | GitHub
45 | GitLab
46 | APKPure
47 | Hakkında
48 | Sıklık
49 | Tema
50 | Sistem
51 | Koyu
52 | Açık
53 | Kaynak kodunu inceleyin, hataları bildirin, yeni özellikler isteyin ve çeviriler ekleyin.
54 | Uygulamayı beğendiyseniz bağış yapmayı düşünün.
55 | Panoya kopyala
56 | Uygulama Listesini Kopyala
57 | Güncellemeler
58 | Güncelleme bildirimleri için kanal.
59 | updateChannel
60 | Güncellemeler
61 |
62 | - Güncelleme bulunamadı.
63 | - %1$d güncelleme bulundu.
64 | - %1$d güncelleme bulundu.
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 应用
3 | 搜索
4 | 更新
5 | 设置
6 | "出了点问题\n\uD83E\uDD26"
7 | 忽略该版本
8 | 忽略应用
9 | 取消忽略应用
10 | 查找更新
11 | 排除系统应用
12 | 包括系统应用
13 | 排除应用商店
14 | 包括应用商店
15 | 排除已禁用应用
16 | 包括已禁用应用
17 | 安装应用
18 | 应用图标
19 | %1$s 已安装
20 | %1$s 安装失败
21 |
22 |
23 | 不支持用Root安装这个app
24 | 竖屏列数
25 | 横屏列数
26 | 播放文字动画
27 | 来源
28 | 用户界面
29 | 提醒
30 | 选项
31 | 工具
32 | 提醒小时
33 | 每日
34 | 3天
35 | 每周
36 | Android TV 界面
37 | 忽略Alpha版本
38 | 忽略Beta版本
39 | 忽略预发布版本
40 | 使用安全商店(Aptoide)
41 | Root安装
42 | 关于
43 | 频率
44 | 主题
45 | 系统
46 | 深色
47 | 浅色
48 |
49 |
50 | 获取源代码、报告错误、请求新功能以及添加翻译。
51 | 如果你喜欢这个应用程序,请考虑捐赠。
52 | 复制到剪贴板
53 | 复制应用列表
54 | 复制应用日志
55 | 更新
56 | 用于更新通知的通道。
57 | 更新
58 |
59 | - 没有发现更新。
60 | - 发现 %1$d 个更新。
61 | - 发现 %1$d 个更新。
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | 應用程式
5 | 搜尋
6 | 更新
7 | 設定
8 | "發生錯誤\n\uD83E\uDD26"
9 | 忽略版本
10 | 忽略應用程式
11 | 取消忽略應用程式
12 | 尋找更新
13 | 排除系統應用程式
14 | 包含系統應用程式
15 | 排除應用程式商店
16 | 包含應用程式商店
17 | 排除已停用的應用程式
18 | 包含已停用的應用程式
19 | 安裝應用程式
20 | 應用程式圖示
21 | %1$s 已安裝。
22 | %1$s 安裝失敗。
23 | 不支援此應用程式的 Root 安裝。
24 |
25 | // Settings
26 | 直向欄數
27 | 橫向欄數
28 | 播放文字動畫
29 | 來源
30 | 使用者介面
31 | 鬧鐘
32 | 選項
33 | 工具
34 | 鬧鐘時間
35 | 每日
36 | 每三天
37 | 每週
38 | Android TV UI
39 | 忽略 Alpha 版
40 | 忽略 Beta 版
41 | 忽略預發布版
42 | 使用安全商店 (Aptoide)
43 | Root 安裝
44 | ApkMirror
45 | F-Droid Main
46 | F-Droid Izzy
47 | Aptoide
48 | GitHub
49 | GitLab
50 | APKPure
51 | Play
52 | 關於
53 | 頻率
54 | 主題
55 | 系統
56 | 深色
57 | 淺色
58 | 取得原始碼、回報錯誤、請求新功能並添加翻譯。
59 | 如果你喜歡這個應用程式,請考慮捐贈。
60 | 複製到剪貼簿
61 | 複製應用程式列表
62 | 複製應用程式日誌
63 |
64 | // Notifications
65 | 更新
66 | 更新通知的頻道。
67 | updateChannel
68 | 更新
69 |
70 | - 沒有找到更新。
71 | - 找到 %1$d 個更新。
72 | - 找到 %1$d 個更新。
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKUpdater
4 | Apps
5 | Search
6 | Updates
7 | Settings
8 | "Something went wrong\n\uD83E\uDD26"
9 | Ignore Version
10 | Ignore App
11 | Unignore App
12 | Look for Updates
13 | Exclude System Apps
14 | Include System Apps
15 | Exclude App Store
16 | Include App Store
17 | Exclude Disabled Apps
18 | Include Disabled Apps
19 | Install App
20 | App Icon
21 | %1$s installed.
22 | %1$s failed to install.
23 | Root install of this app is not supported.
24 |
25 | // Settings
26 | Portrait Columns
27 | Landscape Columns
28 | Play Text Animations
29 | Sources
30 | UI
31 | Alarm
32 | Options
33 | Utils
34 | Alarm Hour
35 | Daily
36 | 3-Day
37 | Weekly
38 | Android TV UI
39 | Ignore Alpha
40 | Ignore Beta
41 | Ignore Pre-release
42 | Use Safe Stores (Aptoide)
43 | Root Install
44 | ApkMirror
45 | F-Droid Main
46 | F-Droid Izzy
47 | Aptoide
48 | GitHub
49 | GitLab
50 | APKPure
51 | Play
52 | About
53 | Frequency
54 | Theme
55 | System
56 | Dark
57 | Light
58 | Get the source code, report bugs, request new features and add translations.
59 | If you like the app, consider donating to a worthy cause.
60 | Copy to clipboard
61 | Copy App List
62 | Copy App Logs
63 |
64 | // Notifications
65 | Updates
66 | Channel for update notifications.
67 | updateChannel
68 | Updates
69 |
70 | - No updates found.
71 | - Found %1$d update.
72 | - Found %1$d updates.
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/apkupdater/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.apkupdater
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application") version "8.3.1" apply false
3 | id("org.jetbrains.kotlin.android") version "1.9.0" apply false
4 | }
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/short_description.txt:
--------------------------------------------------------------------------------
1 | Updates von verschiedenen Quellen beziehen
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | # APKUpdater [](https://github.com/rumboalla/apkupdater/actions?query=workflow%3A%22Android+Build%22)
2 | **APKUpdater** is an open source tool that simplifies the process of **finding updates** for your installed apps.
3 | It provides similar functionality to an app store, but instead of depending on a single source, it aggregates the results from **APKMirror**, **Aptoide**, **F-Droid** and **GitHub**.
4 |
5 | The 3.x branch is a full rewrite using modern technologies like **Jetpack Compose**, **Flow** and **WorkManager**.
6 |
7 | # Features
8 | * **Update Sources**: Find updates from **APKMirror**, **Aptoide**, **F-Droid** and **GitHub**.
9 | * **Search Sources**: Find new apps to install from **APKMirror**, **Aptoide**, **F-Droid** and **GitHub**.
10 | * Schedule **background update checks** and receive a **notification** when updates are found.
11 | * Supports **Android 5** (**21**) to **Android 14** (**34**).
12 | * Supports **Android TV**.
13 | * **Material Design 3** with **Dark**, **Light** and **System** theme support.
14 | * Supports **Material You** on Android 12+.
15 | * **Direct install** of updates for sources that support it.
16 | * Supports **installs without user interaction** on Android 12+.
17 | * **Root install** of updates.
18 | * No ads. No tracking.
19 | * **Languages**: English, Spanish, Dutch, German, Traditional Chinese, Simplified Chinese, Romanian, Italian, Portuguese, Russian, Turkish.
20 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Get updates from multiple sources
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
2 | android.useAndroidX=true
3 | kotlin.code.style=official
4 | android.nonTransitiveRClass=true
5 | android.nonFinalResIds=false
6 | android.suppressUnsupportedCompileSdk=34
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rumboalla/apkupdater/c55d9175cddfa50d377a1dd313873510e36517a2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
3 | distributionPath=wrapper/dists
4 | zipStorePath=wrapper/dists
5 | zipStoreBase=GRADLE_USER_HOME
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | maven("https://jitpack.io")
15 | maven("https://gitlab.com/api/v4/projects/18497829/packages/maven")
16 | }
17 | }
18 |
19 | rootProject.name = "apkupdater"
20 | include(":app")
21 |
--------------------------------------------------------------------------------