diff --git a/Foodify/app/build.gradle b/Foodify/app/build.gradle index 6b0a72d..00935cc 100644 --- a/Foodify/app/build.gradle +++ b/Foodify/app/build.gradle @@ -10,14 +10,13 @@ android { minSdk 23 targetSdk 30 versionCode 1 - versionName "88.0" + versionName "87.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -90,7 +89,8 @@ dependencies { implementation 'com.daimajia.slider:library:1.1.5@aar' implementation "androidx.browser:browser:1.3.0" - implementation 'com.github.Piashsarker:AndroidAppUpdateLibrary:1.0.4' + implementation project(':appupdate') + } diff --git a/Foodify/app/src/main/AndroidManifest.xml b/Foodify/app/src/main/AndroidManifest.xml index 45764a2..849dc36 100644 --- a/Foodify/app/src/main/AndroidManifest.xml +++ b/Foodify/app/src/main/AndroidManifest.xml @@ -11,8 +11,6 @@ - - - - - - diff --git a/Foodify/app/src/main/java/com/waterbase/foodify/Home.java b/Foodify/app/src/main/java/com/waterbase/foodify/Home.java index e3f7889..7688e96 100644 --- a/Foodify/app/src/main/java/com/waterbase/foodify/Home.java +++ b/Foodify/app/src/main/java/com/waterbase/foodify/Home.java @@ -38,11 +38,11 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.andremion.counterfab.CounterFab; +import com.azhon.appupdate.manager.DownloadManager; import com.daimajia.slider.library.Animations.DescriptionAnimation; import com.daimajia.slider.library.SliderLayout; import com.daimajia.slider.library.SliderTypes.BaseSliderView; import com.daimajia.slider.library.SliderTypes.TextSliderView; -import com.dcastalia.localappupdate.DownloadApk; import com.firebase.ui.database.FirebaseRecyclerAdapter; import com.firebase.ui.database.FirebaseRecyclerOptions; import com.google.android.gms.tasks.OnCompleteListener; @@ -98,6 +98,8 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati HashMap image_list; SliderLayout mSlider; + DownloadManager manager = null; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -180,7 +182,7 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati database = FirebaseDatabase.getInstance(); category = database.getReference("Category"); - if(Double.parseDouble(versionNameApp) < Double.parseDouble(Common.versionAppNewest)){ + if (Double.parseDouble(versionNameApp) < Double.parseDouble(Common.versionAppNewest)) { alertDialogUpdate(); } @@ -239,36 +241,30 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati //Setup Slider setupSlider(); - if(!NotificationManagerCompat.from(this).areNotificationsEnabled()){ + if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) { showNotificationAlertDialog("Hãy bật quyền thông báo trên thiết bị của bạn để chúng thôi có thể cung cấp thông tin cho bạn một cách nhanh nhất!"); } - + } private void alertDialogUpdate() { - AlertDialog alertDialog = new AlertDialog.Builder(Home.this) - .setTitle("Thông báo!") - .setMessage("Hiện tại đã có phiên bản mới hơn! Vui lòng cập nhật ứng dụng để mang đến những trải nghiệm tốt nhất!") - .setPositiveButton("Đồng ý", null) - .setNegativeButton("Để sau", null) - .create(); + String url = "https://github.com/Nezumi2711/PRM392/raw/main/app-debug.apk"; + manager = new DownloadManager.Builder(Home.this) + .apkUrl(url) + .apkName("app-debug.apk") + .smallIcon(R.mipmap.ic_launcher) + .apkVersionCode(3) + .apkSize("26.5") + .showNotification(false) + .showBgdToast(false) + .apkVersionName(Common.versionAppNewest + ".0 ") + .apkDescription("1. Sửa lỗi đặt hàng!\n2. Cải thiện hiệu suất\n3. Tối ưu giao diện\n4. Cập nhật thư viện app.") + .dialogProgressBarColor(0xFFFF5353) + .jumpInstallPage(false) + .build(); - alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - Button agree = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - - agree.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - checkWriteExternalStoragePermission(); - alertDialog.dismiss(); - } - }); - } - }); - alertDialog.show(); + manager.download(); } private void alertDialogNewest() { @@ -280,44 +276,6 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati alertDialog.show(); } - private void checkWriteExternalStoragePermission() { - - if (ActivityCompat.checkSelfPermission(Home.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - // If we have permission than we can Start the Download the task - downloadTask(); - } else { - // If we don't have permission than requesting the permission - requestWriteExternalStoragePermission(); - } - } - - private void requestWriteExternalStoragePermission() { - if (ActivityCompat.checkSelfPermission(Home.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(Home.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); - } else{ - ActivityCompat.requestPermissions(Home.this,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode==MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE && grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadTask(); - } else { - Toast.makeText(Home.this, "Permission Not Granted.", Toast.LENGTH_SHORT).show(); - } - } - - private void downloadTask() { - // This @DownloadApk class is provided by our library - // Pass the Context when creating object of DownloadApk - - String url = "https://github.com/Nezumi2711/PRM392/raw/main/app-debug.apk"; - DownloadApk downloadApk = new DownloadApk(Home.this); - downloadApk.startDownloadingApk(url, "Update 2.0"); - } - private void showNotificationAlertDialog(String msg) { AlertDialog alertDialog = new AlertDialog.Builder(Home.this) .setTitle("Thông báo!") @@ -358,7 +316,7 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati alertDialog.show(); TextView textView = (TextView) alertDialog.findViewById(android.R.id.message); - Typeface face=Typeface.createFromAsset(getAssets(),"fonts/regular.ttf"); + Typeface face = Typeface.createFromAsset(getAssets(), "fonts/regular.ttf"); textView.setTypeface(face); } @@ -479,7 +437,7 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati } else if (id == R.id.nav_favorites) { startActivity(new Intent(Home.this, FavoritesActivity.class)); } else if (id == R.id.nav_update) { - if(Double.parseDouble(versionNameApp) < Double.parseDouble(Common.versionAppNewest)){ + if (Double.parseDouble(versionNameApp) < Double.parseDouble(Common.versionAppNewest)) { alertDialogUpdate(); } else { alertDialogNewest(); @@ -529,7 +487,7 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati FirebaseMessaging.getInstance().subscribeToTopic(Common.topicName); //Write value Paper.book().write("sub_new", "true"); - if(!NotificationManagerCompat.from(Home.this).areNotificationsEnabled()){ + if (!NotificationManagerCompat.from(Home.this).areNotificationsEnabled()) { showNotificationAlertDialog("Oops, máy bạn chưa được cấp quyền thông báo! Bạn có muốn bật để nhận thông báo từ hệ thống không?"); } } else { @@ -652,11 +610,11 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati } } else { waitingDialog.dismiss(); - if(TextUtils.isEmpty(edtPassword.getText().toString())){ + if (TextUtils.isEmpty(edtPassword.getText().toString())) { edtPassword.setError("Vui lòng điền vào trường này!"); - } else if(TextUtils.isEmpty(edtNewPassword.getText().toString())){ + } else if (TextUtils.isEmpty(edtNewPassword.getText().toString())) { edtNewPassword.setError("Vui lòng điền vào trường này!"); - } else if(TextUtils.isEmpty(edtRepeatPassword.getText().toString())){ + } else if (TextUtils.isEmpty(edtRepeatPassword.getText().toString())) { edtRepeatPassword.setError("Vui lòng điền vào trường này!"); } } @@ -684,7 +642,7 @@ public class Home extends AppCompatActivity implements NavigationView.OnNavigati @Override public void onBackPressed() { - if(backPressedTime + 2000 > System.currentTimeMillis()) { + if (backPressedTime + 2000 > System.currentTimeMillis()) { super.onBackPressed(); return; } else { diff --git a/Foodify/app/src/main/res/values/strings.xml b/Foodify/app/src/main/res/values/strings.xml index a4e4f74..6983631 100644 --- a/Foodify/app/src/main/res/values/strings.xml +++ b/Foodify/app/src/main/res/values/strings.xml @@ -8,4 +8,14 @@ Navigation header Settings Android Studio + + 一個簡單好用的版本更新庫 + 發現新版本 + 1.支持Android4.1及以上版本\n2.支持自定義下載過程\n3.支持通知欄進度條展示\n4.支持文字國際化\n5.使用Kotlin協程重構 + 升級 + 使用說明:三個方式不能並行 請等待一個方式下載完成在使用其他方式 + 使用自己的對話框更新 + 簡單易用 + 使用版本庫內置的對話框更新 + 取消下載 \ No newline at end of file diff --git a/Foodify/appupdate/.gitignore b/Foodify/appupdate/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Foodify/appupdate/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Foodify/appupdate/build.gradle b/Foodify/appupdate/build.gradle new file mode 100644 index 0000000..4eaa898 --- /dev/null +++ b/Foodify/appupdate/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 32 + + defaultConfig { + minSdk 23 + targetSdk 32 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0" + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/Foodify/appupdate/consumer-rules.pro b/Foodify/appupdate/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/Foodify/appupdate/proguard-rules.pro b/Foodify/appupdate/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Foodify/appupdate/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Foodify/appupdate/src/androidTest/java/com/azhon/appupdate/ExampleInstrumentedTest.kt b/Foodify/appupdate/src/androidTest/java/com/azhon/appupdate/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..a68f06f --- /dev/null +++ b/Foodify/appupdate/src/androidTest/java/com/azhon/appupdate/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.azhon.appupdate + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.azhon.appupdate.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/AndroidManifest.xml b/Foodify/appupdate/src/main/AndroidManifest.xml new file mode 100644 index 0000000..887a15e --- /dev/null +++ b/Foodify/appupdate/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/BaseHttpDownloadManager.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/BaseHttpDownloadManager.kt new file mode 100644 index 0000000..4335d57 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/BaseHttpDownloadManager.kt @@ -0,0 +1,34 @@ +package com.azhon.appupdate.base + +import com.azhon.appupdate.base.bean.DownloadStatus +import kotlinx.coroutines.flow.Flow + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.base + * FileName: BaseHttpDownloadManager + * CreateDate: 2022/4/7 on 10:24 + * Desc: + * + * @author azhon + */ + +abstract class BaseHttpDownloadManager { + /** + * download apk from apkUrl + * + * @param apkUrl + * @param apkName + */ + abstract fun download(apkUrl: String, apkName: String): Flow + + /** + * cancel download apk + */ + abstract fun cancel() + + /** + * release memory + */ + abstract fun release() +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/bean/DownloadStatus.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/bean/DownloadStatus.kt new file mode 100644 index 0000000..065771d --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/base/bean/DownloadStatus.kt @@ -0,0 +1,28 @@ +package com.azhon.appupdate.base.bean + +import java.io.File + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.base.bean + * FileName: DownloadStatus + * CreateDate: 2022/4/14 on 11:18 + * Desc: + * + * @author azhon + */ + + +sealed class DownloadStatus { + + object Start : DownloadStatus() + + data class Downloading(val max: Int, val progress: Int) : DownloadStatus() + + class Done(val apk: File) : DownloadStatus() + + object Cancel : DownloadStatus() + + data class Error(val e: Throwable) : DownloadStatus() +} diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/AppUpdateFileProvider.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/AppUpdateFileProvider.kt new file mode 100644 index 0000000..9ba7ff4 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/AppUpdateFileProvider.kt @@ -0,0 +1,16 @@ +package com.azhon.appupdate.config + +import androidx.core.content.FileProvider + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.config + * FileName: AppUpdateFileProvider + * CreateDate: 2022/4/7 on 10:30 + * Desc: + * + * @author azhon + */ + + +class AppUpdateFileProvider : FileProvider() \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/Constant.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/Constant.kt new file mode 100644 index 0000000..41f9951 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/config/Constant.kt @@ -0,0 +1,59 @@ +package com.azhon.appupdate.config + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.config + * FileName: Constant + * CreateDate: 2022/4/7 on 10:28 + * Desc: + * + * @author azhon + */ + +object Constant { + + /** + * Http timeout(ms) + */ + const val HTTP_TIME_OUT = 30_000 + + /** + * Logcat tag + */ + const val TAG = "AppUpdate." + + /** + * Apk file extension + */ + const val APK_SUFFIX = ".apk" + + /** + * Coroutine Name + */ + const val COROUTINE_NAME = "app-update-coroutine" + + /** + * Notification channel id + */ + const val DEFAULT_CHANNEL_ID = "appUpdate" + + /** + * Notification id + */ + const val DEFAULT_NOTIFY_ID = 1011 + + /** + * Notification channel name + */ + const val DEFAULT_CHANNEL_NAME = "AppUpdate" + + /** + * Compat Android N file uri + */ + var AUTHORITIES: String? = null + + /** + * Apk path + */ + var APK_PATH = "/storage/emulated/0/Android/data/%s/cache" +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/LifecycleCallbacksAdapter.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/LifecycleCallbacksAdapter.kt new file mode 100644 index 0000000..c10468e --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/LifecycleCallbacksAdapter.kt @@ -0,0 +1,40 @@ +package com.azhon.appupdate.listener + +import android.app.Activity +import android.app.Application +import android.os.Bundle + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.listener + * FileName: LifecycleCallbacksAdapter + * CreateDate: 2022/4/8 on 11:26 + * Desc: + * + * @author azhon + */ + +abstract class LifecycleCallbacksAdapter : Application.ActivityLifecycleCallbacks { + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + } + + override fun onActivityStarted(activity: Activity) { + } + + override fun onActivityResumed(activity: Activity) { + } + + override fun onActivityPaused(activity: Activity) { + } + + override fun onActivityStopped(activity: Activity) { + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + } + + override fun onActivityDestroyed(activity: Activity) { + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnButtonClickListener.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnButtonClickListener.kt new file mode 100644 index 0000000..3b3cbcc --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnButtonClickListener.kt @@ -0,0 +1,28 @@ +package com.azhon.appupdate.listener + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.listener + * FileName: OnButtonClickListener + * CreateDate: 2022/4/7 on 15:56 + * Desc: + * + * @author azhon + */ + +interface OnButtonClickListener { + companion object { + /** + * click update button + */ + const val UPDATE = 0 + + /** + * click cancel button + */ + const val CANCEL = 1 + } + + fun onButtonClick(id: Int) +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListener.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListener.kt new file mode 100644 index 0000000..aa8e00d --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListener.kt @@ -0,0 +1,43 @@ +package com.azhon.appupdate.listener + +import java.io.File + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.listener + * FileName: OnDownloadListener + * CreateDate: 2022/4/7 on 10:27 + * Desc: + * + * @author azhon + */ + +interface OnDownloadListener { + /** + * start download + */ + fun start() + + /** + * + * @param max file length + * @param progress downloaded file size + */ + fun downloading(max: Int, progress: Int) + + /** + * @param apk + */ + fun done(apk: File) + + /** + * cancel download + */ + fun cancel() + + /** + * + * @param e + */ + fun error(e: Throwable) +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListenerAdapter.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListenerAdapter.kt new file mode 100644 index 0000000..0c4c985 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/listener/OnDownloadListenerAdapter.kt @@ -0,0 +1,31 @@ +package com.azhon.appupdate.listener + +import java.io.File + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.listener + * FileName: OnDownloadListenerAdapter + * CreateDate: 2022/4/8 on 10:58 + * Desc: + * + * @author azhon + */ + +abstract class OnDownloadListenerAdapter : OnDownloadListener { + override fun start() { + } + + override fun downloading(max: Int, progress: Int) { + } + + override fun done(apk: File) { + } + + override fun cancel() { + } + + override fun error(e: Throwable) { + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/DownloadManager.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/DownloadManager.kt new file mode 100644 index 0000000..264f60f --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/DownloadManager.kt @@ -0,0 +1,437 @@ +package com.azhon.appupdate.manager + +import android.app.Activity +import android.app.Application +import android.app.NotificationChannel +import android.content.Intent +import android.widget.Toast +import com.azhon.appupdate.R +import com.azhon.appupdate.base.BaseHttpDownloadManager +import com.azhon.appupdate.config.Constant +import com.azhon.appupdate.listener.LifecycleCallbacksAdapter +import com.azhon.appupdate.listener.OnButtonClickListener +import com.azhon.appupdate.listener.OnDownloadListener +import com.azhon.appupdate.service.DownloadService +import com.azhon.appupdate.util.ApkUtil +import com.azhon.appupdate.util.LogUtil +import com.azhon.appupdate.view.UpdateDialogActivity +import java.io.Serializable + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.manager + * FileName: DownloadManager + * CreateDate: 2022/4/7 on 10:36 + * Desc: + * + * @author azhon + */ +class DownloadManager private constructor(builder: Builder) : Serializable { + + companion object { + private const val TAG = "DownloadManager" + private var instance: DownloadManager? = null + + fun getInstance(builder: Builder? = null): DownloadManager? { + if (instance == null) { + if (builder == null) return null + instance = DownloadManager(builder) + } + return instance!! + } + } + + var application: Application = builder.application + var contextClsName: String = builder.contextClsName + var downloadState: Boolean = false + var apkUrl: String + var apkName: String + var apkVersionCode: Int + var apkVersionName: String + var downloadPath: String + var showNewerToast: Boolean + var smallIcon: Int + var apkDescription: String + var apkSize: String + var apkMD5: String + var httpManager: BaseHttpDownloadManager? + var notificationChannel: NotificationChannel? + var onDownloadListeners: MutableList + var onButtonClickListener: OnButtonClickListener? + var showNotification: Boolean + var jumpInstallPage: Boolean + var showBgdToast: Boolean + var forcedUpgrade: Boolean + var notifyId: Int + var dialogImage: Int + var dialogButtonColor: Int + var dialogButtonTextColor: Int + var dialogProgressBarColor: Int + + + init { + apkUrl = builder.apkUrl + apkName = builder.apkName + apkVersionCode = builder.apkVersionCode + apkVersionName = builder.apkVersionName + downloadPath = + builder.downloadPath ?: String.format(Constant.APK_PATH, application.packageName) + showNewerToast = builder.showNewerToast + smallIcon = builder.smallIcon + apkDescription = builder.apkDescription + apkSize = builder.apkSize + apkMD5 = builder.apkMD5 + httpManager = builder.httpManager + notificationChannel = builder.notificationChannel + onDownloadListeners = builder.onDownloadListeners + onButtonClickListener = builder.onButtonClickListener + showNotification = builder.showNotification + jumpInstallPage = builder.jumpInstallPage + showBgdToast = builder.showBgdToast + forcedUpgrade = builder.forcedUpgrade + notifyId = builder.notifyId + dialogImage = builder.dialogImage + dialogButtonColor = builder.dialogButtonColor + dialogButtonTextColor = builder.dialogButtonTextColor + dialogProgressBarColor = builder.dialogProgressBarColor + // Fix memory leak + application.registerActivityLifecycleCallbacks(object : LifecycleCallbacksAdapter() { + override fun onActivityDestroyed(activity: Activity) { + super.onActivityDestroyed(activity) + if (contextClsName == activity.javaClass.name) { + clearListener() + } + } + }) + } + + /** + * Start download + */ + fun download() { + if (!checkParams()) { + return + } + if (checkVersionCode()) { + application.startService(Intent(application, DownloadService::class.java)) + } else { + if (apkVersionCode > ApkUtil.getVersionCode(application)) { + application.startActivity( + Intent(application, UpdateDialogActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } else { + if (showNewerToast) { + Toast.makeText(application, R.string.latest_version, Toast.LENGTH_SHORT).show() + } + LogUtil.d(TAG, application.resources.getString(R.string.latest_version)) + } + } + + } + + private fun checkParams(): Boolean { + if (apkUrl.isEmpty()) { + LogUtil.e(TAG, "apkUrl can not be empty!") + return false + } + if (apkName.isEmpty()) { + LogUtil.e(TAG, "apkName can not be empty!") + return false + } + if (!apkName.endsWith(Constant.APK_SUFFIX)) { + LogUtil.e(TAG, "apkName must endsWith .apk!") + return false + } + if (smallIcon == -1) { + LogUtil.e(TAG, "smallIcon can not be empty!"); + return false + } + Constant.AUTHORITIES = "${application.packageName}.fileProvider" + return true + } + + /** + * Check the set apkVersionCode if it is not the default then use the built-in dialog + * If it is the default value Int.MIN_VALUE, directly start the service download + */ + private fun checkVersionCode(): Boolean { + if (apkVersionCode == Int.MIN_VALUE) { + return true + } + if (apkDescription.isEmpty()) { + LogUtil.e(TAG, "apkDescription can not be empty!") + } + return false + } + + fun cancel() { + httpManager?.cancel() + } + + /** + * release objects + */ + internal fun release() { + instance = null + clearListener() + } + + private fun clearListener() { + onButtonClickListener = null + onDownloadListeners.clear() + } + + class Builder constructor(activity: Activity) { + + /** + * library context + */ + internal var application: Application = activity.application + + /** + * Fix the memory leak caused by Activity destroy + */ + internal var contextClsName: String = activity.javaClass.name + + /** + * Apk download url + */ + internal var apkUrl = "" + + /** + * Apk file name on disk + */ + internal var apkName = "" + + /** + * The apk versionCode that needs to be downloaded + */ + internal var apkVersionCode = Int.MIN_VALUE + + /** + * The versionName of the dialog reality + */ + internal var apkVersionName = "" + + /** + * The file path where the Apk is saved + * eg: /storage/emulated/0/Android/data/ your packageName /cache + */ + internal var downloadPath = application.externalCacheDir?.path + + /** + * whether to tip to user "Currently the latest version!" + */ + internal var showNewerToast = false + + /** + * Notification icon resource + */ + internal var smallIcon = -1 + + /** + * New version description information + */ + internal var apkDescription = "" + + /** + * Apk Size,Unit MB + */ + internal var apkSize = "" + + /** + * Apk md5 file verification(32-bit) verification repeated download + */ + internal var apkMD5 = "" + + /** + * Apk download manager + */ + internal var httpManager: BaseHttpDownloadManager? = null + + /** + * The following are unimportant filed + */ + + /** + * adapter above Android O notification + */ + internal var notificationChannel: NotificationChannel? = null + + /** + * download listeners + */ + internal var onDownloadListeners = mutableListOf() + + /** + * dialog button click listener + */ + internal var onButtonClickListener: OnButtonClickListener? = null + + /** + * Whether to show the progress of the notification + */ + internal var showNotification = true + + /** + * Whether the installation page will pop up automatically after the download is complete + */ + internal var jumpInstallPage = true + + /** + * Does the download start tip "Downloading a new version in the background..." + */ + internal var showBgdToast = true + + /** + * Whether to force an upgrade + */ + internal var forcedUpgrade = false + + /** + * Notification id + */ + internal var notifyId = Constant.DEFAULT_NOTIFY_ID + + /** + * dialog background Image resource + */ + internal var dialogImage = -1 + + /** + * dialog button background color + */ + internal var dialogButtonColor = -1 + + /** + * dialog button text color + */ + internal var dialogButtonTextColor = -1 + + /** + * dialog progress bar color and progress-text color + */ + internal var dialogProgressBarColor = -1 + + + fun apkUrl(apkUrl: String): Builder { + this.apkUrl = apkUrl + return this + } + + fun apkName(apkName: String): Builder { + this.apkName = apkName + return this + } + + fun apkVersionCode(apkVersionCode: Int): Builder { + this.apkVersionCode = apkVersionCode + return this + } + + fun apkVersionName(apkVersionName: String): Builder { + this.apkVersionName = apkVersionName + return this + } + + fun showNewerToast(showNewerToast: Boolean): Builder { + this.showNewerToast = showNewerToast + return this + } + + fun smallIcon(smallIcon: Int): Builder { + this.smallIcon = smallIcon + return this + } + + fun apkDescription(apkDescription: String): Builder { + this.apkDescription = apkDescription + return this + } + + fun apkSize(apkSize: String): Builder { + this.apkSize = apkSize + return this + } + + fun apkMD5(apkMD5: String): Builder { + this.apkMD5 = apkMD5 + return this + } + + fun httpManager(httpManager: BaseHttpDownloadManager): Builder { + this.httpManager = httpManager + return this + } + + fun notificationChannel(notificationChannel: NotificationChannel): Builder { + this.notificationChannel = notificationChannel + return this + } + + fun onButtonClickListener(onButtonClickListener: OnButtonClickListener): Builder { + this.onButtonClickListener = onButtonClickListener + return this + } + + fun onDownloadListener(onDownloadListener: OnDownloadListener): Builder { + this.onDownloadListeners.add(onDownloadListener) + return this + } + + fun showNotification(showNotification: Boolean): Builder { + this.showNotification = showNotification + return this + } + + fun jumpInstallPage(jumpInstallPage: Boolean): Builder { + this.jumpInstallPage = jumpInstallPage + return this + } + + fun showBgdToast(showBgdToast: Boolean): Builder { + this.showBgdToast = showBgdToast + return this + } + + fun forcedUpgrade(forcedUpgrade: Boolean): Builder { + this.forcedUpgrade = forcedUpgrade + return this + } + + fun notifyId(notifyId: Int): Builder { + this.notifyId = notifyId + return this + } + + fun dialogImage(dialogImage: Int): Builder { + this.dialogImage = dialogImage + return this + } + + fun dialogButtonColor(dialogButtonColor: Int): Builder { + this.dialogButtonColor = dialogButtonColor + return this + } + + fun dialogButtonTextColor(dialogButtonTextColor: Int): Builder { + this.dialogButtonTextColor = dialogButtonTextColor + return this + } + + fun dialogProgressBarColor(dialogProgressBarColor: Int): Builder { + this.dialogProgressBarColor = dialogProgressBarColor + return this + } + + fun enableLog(enable: Boolean): Builder { + LogUtil.enable(enable) + return this + } + + fun build(): DownloadManager { + return getInstance(this)!! + } + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/HttpDownloadManager.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/HttpDownloadManager.kt new file mode 100644 index 0000000..580188f --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/manager/HttpDownloadManager.kt @@ -0,0 +1,134 @@ +package com.azhon.appupdate.manager + +import com.azhon.appupdate.base.BaseHttpDownloadManager +import com.azhon.appupdate.base.bean.DownloadStatus +import com.azhon.appupdate.config.Constant +import com.azhon.appupdate.util.LogUtil +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import java.io.File +import java.io.FileOutputStream +import java.net.HttpURLConnection +import java.net.SocketTimeoutException +import java.net.URL +import java.security.SecureRandom +import java.security.cert.X509Certificate +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.manager + * FileName: HttpDownloadManager + * CreateDate: 2022/4/7 on 14:29 + * Desc: + * + * @author azhon + */ + +@Suppress("BlockingMethodInNonBlockingContext") +class HttpDownloadManager(private val path: String) : BaseHttpDownloadManager() { + companion object { + private const val TAG = "HttpDownloadManager" + } + + private var shutdown: Boolean = false + + override fun download(apkUrl: String, apkName: String): Flow { + trustAllHosts() + shutdown = false + File(path, apkName).let { + if (it.exists()) it.delete() + } + return flow { + emit(DownloadStatus.Start) + connectToDownload(apkUrl, apkName, this) + }.catch { + emit(DownloadStatus.Error(it)) + }.flowOn(Dispatchers.IO) + } + + private suspend fun connectToDownload( + apkUrl: String, apkName: String, flow: FlowCollector + ) { + val con = URL(apkUrl).openConnection() as HttpURLConnection + con.apply { + requestMethod = "GET" + readTimeout = Constant.HTTP_TIME_OUT + connectTimeout = Constant.HTTP_TIME_OUT + setRequestProperty("Accept-Encoding", "identity") + } + if (con.responseCode == HttpURLConnection.HTTP_OK) { + val inStream = con.inputStream + val length = con.contentLength + var len: Int + var progress = 0 + val buffer = ByteArray(1024 * 2) + val file = File(path, apkName) + FileOutputStream(file).use { out -> + while (inStream.read(buffer).also { len = it } != -1 && !shutdown) { + out.write(buffer, 0, len) + progress += len + flow.emit(DownloadStatus.Downloading(length, progress)) + } + out.flush() + } + inStream.close() + if (shutdown) { + flow.emit(DownloadStatus.Cancel) + } else { + flow.emit(DownloadStatus.Done(file)) + } + } else if (con.responseCode == HttpURLConnection.HTTP_MOVED_PERM + || con.responseCode == HttpURLConnection.HTTP_MOVED_TEMP + ) { + con.disconnect() + val locationUrl = con.getHeaderField("Location") + LogUtil.d( + TAG, + "The current url is the redirect Url, the redirected url is $locationUrl" + ) + connectToDownload(locationUrl, apkName, flow) + } else { + val e = SocketTimeoutException("Error: Http response code = ${con.responseCode}") + flow.emit(DownloadStatus.Error(e)) + } + con.disconnect() + } + + /** + * fix https url (SSLHandshakeException) exception + */ + private fun trustAllHosts() { + val manager: TrustManager = object : X509TrustManager { + override fun getAcceptedIssuers(): Array { + return arrayOf() + } + + override fun checkClientTrusted(chain: Array?, authType: String?) { + LogUtil.d(TAG, "checkClientTrusted") + } + + override fun checkServerTrusted(chain: Array?, authType: String?) { + LogUtil.d(TAG, "checkServerTrusted") + } + } + try { + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, arrayOf(manager), SecureRandom()) + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) + } catch (e: Exception) { + LogUtil.e(TAG, "trustAllHosts error: $e") + } + } + + override fun cancel() { + shutdown = true + } + + override fun release() { + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/service/DownloadService.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/service/DownloadService.kt new file mode 100644 index 0000000..3fc55b3 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/service/DownloadService.kt @@ -0,0 +1,188 @@ +package com.azhon.appupdate.service + +import android.app.Service +import android.content.Intent +import android.os.Build +import android.os.IBinder +import android.widget.Toast +import com.azhon.appupdate.R +import com.azhon.appupdate.base.bean.DownloadStatus +import com.azhon.appupdate.config.Constant +import com.azhon.appupdate.listener.OnDownloadListener +import com.azhon.appupdate.manager.DownloadManager +import com.azhon.appupdate.manager.HttpDownloadManager +import com.azhon.appupdate.util.ApkUtil +import com.azhon.appupdate.util.FileUtil +import com.azhon.appupdate.util.LogUtil +import com.azhon.appupdate.util.NotificationUtil +import kotlinx.coroutines.* +import java.io.File + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.service + * FileName: DownloadService + * CreateDate: 2022/4/7 on 11:42 + * Desc: + * + * @author azhon + */ + +class DownloadService : Service(), OnDownloadListener { + companion object { + private const val TAG = "DownloadService" + } + + private lateinit var manager: DownloadManager + private var lastProgress = 0 + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent == null) { + return START_NOT_STICKY + } + init() + return super.onStartCommand(intent, flags, startId) + } + + private fun init() { + val tempManager = DownloadManager.getInstance() + if (tempManager == null) { + LogUtil.e(TAG, "An exception occurred by DownloadManager=null,please check your code!") + return + } + manager = tempManager + FileUtil.createDirDirectory(manager.downloadPath) + + val enable = NotificationUtil.notificationEnable(this@DownloadService) + LogUtil.d( + TAG, + if (enable) "Notification switch status: opened" else " Notification switch status: closed" + ) + if (checkApkMd5()) { + LogUtil.d(TAG, "Apk already exist and install it directly.") + //install apk + done(File(manager.downloadPath, manager.apkName)) + } else { + LogUtil.d(TAG, "Apk don't exist will start download.") + download() + } + } + + /** + * Check whether the Apk has been downloaded, don't download again + */ + private fun checkApkMd5(): Boolean { + val file = File(manager.downloadPath, manager.apkName) + if (file.exists()) { + return FileUtil.md5(file).equals(manager.apkMD5, ignoreCase = true) + } + return false + } + + @Synchronized + private fun download() { + if (manager.downloadState) { + LogUtil.e(TAG, "Currently downloading, please download again!") + return + } + if (manager.httpManager == null) { + manager.httpManager = HttpDownloadManager(manager.downloadPath) + } + GlobalScope.launch(Dispatchers.Main + CoroutineName(Constant.COROUTINE_NAME)) { + manager.httpManager!!.download(manager.apkUrl, manager.apkName) + .collect { + when (it) { + is DownloadStatus.Start -> start() + is DownloadStatus.Downloading -> downloading(it.max, it.progress) + is DownloadStatus.Done -> done(it.apk) + is DownloadStatus.Cancel -> this@DownloadService.cancel() + is DownloadStatus.Error -> error(it.e) + } + } + } + manager.downloadState = true + } + + override fun start() { + LogUtil.i(TAG, "download start") + if (manager.showBgdToast) { + Toast.makeText(this, R.string.background_downloading, Toast.LENGTH_SHORT).show() + } + if (manager.showNotification) { + NotificationUtil.showNotification( + this@DownloadService, manager.smallIcon, + resources.getString(R.string.start_download), + resources.getString(R.string.start_download_hint) + ) + } + manager.onDownloadListeners.forEach { it.start() } + } + + override fun downloading(max: Int, progress: Int) { + if (manager.showNotification) { + val curr = (progress / max.toDouble() * 100.0).toInt() + if (curr == lastProgress) return + LogUtil.i(TAG, "downloading max: $max --- progress: $progress") + lastProgress = curr + val content = if (curr < 0) "" else "$curr%" + NotificationUtil.showProgressNotification( + this@DownloadService, manager.smallIcon, + resources.getString(R.string.start_downloading), + content, if (max == -1) -1 else 100, curr + ) + } + manager.onDownloadListeners.forEach { it.downloading(max, progress) } + } + + override fun done(apk: File) { + LogUtil.d(TAG, "apk downloaded to ${apk.path}") + manager.downloadState = false + //If it is android Q (api=29) and above, (showNotification=false) will also send a + // download completion notification + if (manager.showNotification || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + NotificationUtil.showDoneNotification( + this@DownloadService, manager.smallIcon, + resources.getString(R.string.download_completed), + resources.getString(R.string.click_hint), + Constant.AUTHORITIES!!, apk + ) + } + if (manager.jumpInstallPage) { + ApkUtil.installApk(this@DownloadService, Constant.AUTHORITIES!!, apk) + } + manager.onDownloadListeners.forEach { it.done(apk) } + + // release objects + releaseResources() + } + + override fun cancel() { + LogUtil.i(TAG, "download cancel") + manager.downloadState = false + if (manager.showNotification) { + NotificationUtil.cancelNotification(this@DownloadService) + } + manager.onDownloadListeners.forEach { it.cancel() } + } + + override fun error(e: Throwable) { + LogUtil.e(TAG, "download error: $e") + manager.downloadState = false + if (manager.showNotification) { + NotificationUtil.showErrorNotification( + this@DownloadService, manager.smallIcon, + resources.getString(R.string.download_error), + resources.getString(R.string.continue_downloading), + ) + } + manager.onDownloadListeners.forEach { it.error(e) } + } + + private fun releaseResources() { + manager.httpManager?.release() + manager.release() + stopSelf() + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/ApkUtil.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/ApkUtil.kt new file mode 100644 index 0000000..3207e29 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/ApkUtil.kt @@ -0,0 +1,83 @@ +package com.azhon.appupdate.util + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.PatternMatcher +import androidx.core.content.FileProvider +import java.io.File + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.util + * FileName: ApkUtil + * CreateDate: 2022/4/7 on 17:02 + * Desc: + * + * @author azhon + */ + +class ApkUtil { + companion object { + /** + * install package form file + */ + fun installApk(context: Context, authorities: String, apk: File) { + context.startActivity(createInstallIntent(context, authorities, apk)) + } + + fun createInstallIntent(context: Context, authorities: String, apk: File): Intent { + val intent = Intent().apply { + action = Intent.ACTION_VIEW + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addCategory(Intent.CATEGORY_DEFAULT) + } + val uri: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + uri = FileProvider.getUriForFile(context, authorities, apk) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + uri = Uri.fromFile(apk) + } + intent.setDataAndType(uri, "application/vnd.android.package-archive") + return intent + } + + fun getVersionCode(context: Context): Long { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo.longVersionCode + } else { + return packageInfo.versionCode.toLong() + } + } + + fun deleteOldApk(context: Context, oldApkPath: String): Boolean { + val curVersionCode = getVersionCode(context) + try { + val apk = File(oldApkPath) + if (apk.exists()) { + val oldVersionCode = getVersionCodeByPath(context, oldApkPath) + if (curVersionCode > oldVersionCode) { + return apk.delete() + } + } + } catch (e: Exception) { + } + return false + } + + private fun getVersionCodeByPath(context: Context, path: String): Long { + val packageInfo = + context.packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo?.longVersionCode ?: 1 + } else { + return packageInfo?.versionCode?.toLong() ?: 1 + } + } + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/DensityUtil.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/DensityUtil.kt new file mode 100644 index 0000000..47a85bc --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/DensityUtil.kt @@ -0,0 +1,23 @@ +package com.azhon.appupdate.util + +import android.content.Context + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.util + * FileName: DensityUtil + * CreateDate: 2022/4/7 on 17:52 + * Desc: + * + * @author azhon + */ + +class DensityUtil { + companion object { + fun dip2px(context: Context, dpValue: Float): Float { + val scale = context.resources.displayMetrics.density + return dpValue * scale + 0.5f + } + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/FileUtil.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/FileUtil.kt new file mode 100644 index 0000000..5128bed --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/FileUtil.kt @@ -0,0 +1,46 @@ +package com.azhon.appupdate.util + +import java.io.File +import java.io.FileInputStream +import java.math.BigInteger +import java.security.MessageDigest + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.util + * FileName: FileUtil + * CreateDate: 2022/4/7 on 11:52 + * Desc: + * + * @author azhon + */ + +class FileUtil { + companion object { + fun createDirDirectory(path: String) { + File(path).let { + if (!it.exists()) { + it.mkdirs() + } + } + } + + fun md5(file: File): String { + try { + val buffer = ByteArray(1024) + var len: Int + val digest = MessageDigest.getInstance("MD5") + val inStream = FileInputStream(file) + while (inStream.read(buffer).also { len = it } != -1) { + digest.update(buffer, 0, len) + } + inStream.close() + val bigInt = BigInteger(1, digest.digest()) + return bigInt.toString(16).uppercase() + } catch (e: Exception) { + e.printStackTrace() + } + return "" + } + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/LogUtil.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/LogUtil.kt new file mode 100644 index 0000000..8625c7f --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/LogUtil.kt @@ -0,0 +1,39 @@ +package com.azhon.appupdate.util + +import android.util.Log +import com.azhon.appupdate.config.Constant + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.util + * FileName: LogUtil + * CreateDate: 2022/4/7 on 11:23 + * Desc: + * + * @author azhon + */ + +class LogUtil { + + companion object { + var b = true + + fun enable(enable: Boolean) { + b = enable + } + + fun e(tag: String, msg: String) { + if (b) Log.e(Constant.TAG + tag, msg) + } + + fun d(tag: String, msg: String) { + if (b) Log.d(Constant.TAG + tag, msg) + } + + fun i(tag: String, msg: String) { + if (b) Log.i(Constant.TAG + tag, msg) + } + + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/NotificationUtil.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/NotificationUtil.kt new file mode 100644 index 0000000..c489311 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/util/NotificationUtil.kt @@ -0,0 +1,163 @@ +package com.azhon.appupdate.util + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.azhon.appupdate.config.Constant +import com.azhon.appupdate.manager.DownloadManager +import com.azhon.appupdate.service.DownloadService +import java.io.File + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.util + * FileName: NotificationUtil + * CreateDate: 2022/4/7 on 13:36 + * Desc: + * + * @author azhon + */ +class NotificationUtil { + companion object { + + fun notificationEnable(context: Context): Boolean { + return NotificationManagerCompat.from(context).areNotificationsEnabled() + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private fun getNotificationChannelId(): String { + val channel = DownloadManager.getInstance()?.notificationChannel + return if (channel == null) { + Constant.DEFAULT_CHANNEL_ID + } else { + channel.id + } + } + + private fun builderNotification( + context: Context, icon: Int, title: String, content: String + ): NotificationCompat.Builder { + var channelId = "" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + channelId = getNotificationChannelId() + } + return NotificationCompat.Builder(context, channelId) + .setSmallIcon(icon) + .setContentTitle(title) + .setWhen(System.currentTimeMillis()) + .setContentText(content) + .setAutoCancel(false) + .setOngoing(true) + } + + fun showNotification(context: Context, icon: Int, title: String, content: String) { + val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + afterO(manager) + } + val notify = builderNotification(context, icon, title, content) + .setDefaults(Notification.DEFAULT_SOUND) + .build() + manager.notify( + DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID, + notify + ) + } + + /** + * send a downloading Notification + */ + fun showProgressNotification( + context: Context, icon: Int, title: String, content: String, max: Int, progress: Int + ) { + val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notify = builderNotification(context, icon, title, content) + .setProgress(max, progress, max == -1).build() + manager.notify( + DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID, + notify + ) + } + + /** + * send a downloaded Notification + */ + fun showDoneNotification( + context: Context, icon: Int, title: String, content: String, + authorities: String, apk: File + ) { + val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.cancel(DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID) + val intent = ApkUtil.createInstallIntent(context, authorities, apk) + val pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val notify = builderNotification(context, icon, title, content) + .setContentIntent(pi) + .build() + notify.flags = notify.flags or Notification.FLAG_AUTO_CANCEL + manager.notify( + DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID, + notify + ) + } + + /** + * send a error Notification + */ + fun showErrorNotification( + context: Context, icon: Int, title: String, content: String + ) { + val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + afterO(manager) + } + val intent = Intent(context, DownloadService::class.java) + val pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val notify = builderNotification(context, icon, title, content) + .setAutoCancel(true) + .setOngoing(false) + .setContentIntent(pi) + .setDefaults(Notification.DEFAULT_SOUND) + .build() + manager.notify( + DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID, + notify + ) + } + + /** + * cancel Notification by id + */ + fun cancelNotification(context: Context) { + val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.cancel(DownloadManager.getInstance()?.notifyId ?: Constant.DEFAULT_NOTIFY_ID) + + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private fun afterO(manager: NotificationManager) { + var channel = DownloadManager.getInstance()?.notificationChannel + if (channel == null) { + channel = NotificationChannel( + Constant.DEFAULT_CHANNEL_ID, Constant.DEFAULT_CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW + ).apply { + enableLights(true) + setShowBadge(true) + } + } + manager.createNotificationChannel(channel) + } + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/NumberProgressBar.java b/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/NumberProgressBar.java new file mode 100644 index 0000000..75e4da7 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/NumberProgressBar.java @@ -0,0 +1,479 @@ +package com.azhon.appupdate.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.View; + +import com.azhon.appupdate.R; + + +/** + * Created by daimajia on 14-4-30. + * + */ +public class NumberProgressBar extends View { + + private int mMaxProgress = 100; + + /** + * Current progress, can not exceed the max progress. + */ + private int mCurrentProgress = 0; + + /** + * The progress area bar color. + */ + private int mReachedBarColor; + + /** + * The bar unreached area color. + */ + private int mUnreachedBarColor; + + /** + * The progress text color. + */ + private int mTextColor; + + /** + * The progress text size. + */ + private float mTextSize; + + /** + * The height of the reached area. + */ + private float mReachedBarHeight; + + /** + * The height of the unreached area. + */ + private float mUnreachedBarHeight; + + /** + * The suffix of the number. + */ + private String mSuffix = "%"; + + /** + * The prefix. + */ + private String mPrefix = ""; + + + private final int default_text_color = Color.rgb(255, 137, 91); + private final int default_reached_color = Color.rgb(255, 137, 91); + private final int default_unreached_color = Color.rgb(204, 204, 204); + private final float default_progress_text_offset; + private final float default_text_size; + + /** + * For save and restore instance of progressbar. + */ + private static final String INSTANCE_STATE = "saved_instance"; + private static final String INSTANCE_TEXT_COLOR = "text_color"; + private static final String INSTANCE_TEXT_SIZE = "text_size"; + private static final String INSTANCE_REACHED_BAR_HEIGHT = "reached_bar_height"; + private static final String INSTANCE_REACHED_BAR_COLOR = "reached_bar_color"; + private static final String INSTANCE_UNREACHED_BAR_HEIGHT = "unreached_bar_height"; + private static final String INSTANCE_UNREACHED_BAR_COLOR = "unreached_bar_color"; + private static final String INSTANCE_MAX = "max"; + private static final String INSTANCE_PROGRESS = "progress"; + private static final String INSTANCE_SUFFIX = "suffix"; + private static final String INSTANCE_PREFIX = "prefix"; + private static final String INSTANCE_TEXT_VISIBILITY = "text_visibility"; + + private static final int PROGRESS_TEXT_VISIBLE = 0; + + + /** + * The width of the text that to be drawn. + */ + private float mDrawTextWidth; + + /** + * The drawn text start. + */ + private float mDrawTextStart; + + /** + * The drawn text end. + */ + private float mDrawTextEnd; + + /** + * The text that to be drawn in onDraw(). + */ + private String mCurrentDrawText; + + /** + * The Paint of the reached area. + */ + private Paint mReachedBarPaint; + /** + * The Paint of the unreached area. + */ + private Paint mUnreachedBarPaint; + /** + * The Paint of the progress text. + */ + private Paint mTextPaint; + + /** + * Unreached bar area to draw rect. + */ + private RectF mUnreachedRectF = new RectF(0, 0, 0, 0); + /** + * Reached bar area rect. + */ + private RectF mReachedRectF = new RectF(0, 0, 0, 0); + + /** + * The progress text offset. + */ + private float mOffset; + + /** + * Determine if need to draw unreached area. + */ + private boolean mDrawUnreachedBar = true; + + private boolean mDrawReachedBar = true; + + private boolean mIfDrawText = true; + + public enum ProgressTextVisibility { + Visible, Invisible + } + + public NumberProgressBar(Context context) { + this(context, null); + } + + public NumberProgressBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mReachedBarHeight = dp2px(1.5f); + mUnreachedBarHeight = dp2px(1.0f); + default_text_size = sp2px(10); + default_progress_text_offset = dp2px(3.0f); + + //load styled attributes. + final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberProgressBar, + defStyleAttr, 0); + + mReachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_reached_color, default_reached_color); + mUnreachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_unreached_color, default_unreached_color); + mTextColor = attributes.getColor(R.styleable.NumberProgressBar_progress_text_color, default_text_color); + mTextSize = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_size, default_text_size); + attributes.recycle(); + initializePainters(); + } + + @Override + protected int getSuggestedMinimumWidth() { + return (int) mTextSize; + } + + @Override + protected int getSuggestedMinimumHeight() { + return Math.max((int) mTextSize, Math.max((int) mReachedBarHeight, (int) mUnreachedBarHeight)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false)); + } + + private int measure(int measureSpec, boolean isWidth) { + int result; + int mode = MeasureSpec.getMode(measureSpec); + int size = MeasureSpec.getSize(measureSpec); + int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom(); + if (mode == MeasureSpec.EXACTLY) { + result = size; + } else { + result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); + result += padding; + if (mode == MeasureSpec.AT_MOST) { + if (isWidth) { + result = Math.max(result, size); + } else { + result = Math.min(result, size); + } + } + } + return result; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mIfDrawText) { + calculateDrawRectF(); + } else { + calculateDrawRectFWithoutProgressText(); + } + + if (mDrawReachedBar) { + canvas.drawRect(mReachedRectF, mReachedBarPaint); + } + + if (mDrawUnreachedBar) { + canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint); + } + + if (mIfDrawText) + canvas.drawText(mCurrentDrawText, mDrawTextStart, mDrawTextEnd, mTextPaint); + } + + private void initializePainters() { + mReachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mReachedBarPaint.setColor(mReachedBarColor); + + mUnreachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mUnreachedBarPaint.setColor(mUnreachedBarColor); + + mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setColor(mTextColor); + mTextPaint.setTextSize(mTextSize); + } + + + private void calculateDrawRectFWithoutProgressText() { + mReachedRectF.left = getPaddingLeft(); + mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f; + mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() + getPaddingLeft(); + mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f; + + mUnreachedRectF.left = mReachedRectF.right; + mUnreachedRectF.right = getWidth() - getPaddingRight(); + mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f; + mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f; + } + + private void calculateDrawRectF() { + + mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax()); + mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix; + mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText); + + if (getProgress() == 0) { + mDrawReachedBar = false; + mDrawTextStart = getPaddingLeft(); + } else { + mDrawReachedBar = true; + mReachedRectF.left = getPaddingLeft(); + mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f; + mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() - mOffset + getPaddingLeft(); + mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f; + mDrawTextStart = (mReachedRectF.right + mOffset); + } + + mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f)); + + if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) { + mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth; + mReachedRectF.right = mDrawTextStart - mOffset; + } + + float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset; + if (unreachedBarStart >= getWidth() - getPaddingRight()) { + mDrawUnreachedBar = false; + } else { + mDrawUnreachedBar = true; + mUnreachedRectF.left = unreachedBarStart; + mUnreachedRectF.right = getWidth() - getPaddingRight(); + mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f; + mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f; + } + } + + /** + * Get progress text color. + * + * @return progress text color. + */ + public int getTextColor() { + return mTextColor; + } + + /** + * Get progress text size. + * + * @return progress text size. + */ + public float getProgressTextSize() { + return mTextSize; + } + + public int getUnreachedBarColor() { + return mUnreachedBarColor; + } + + public int getReachedBarColor() { + return mReachedBarColor; + } + + public int getProgress() { + return mCurrentProgress; + } + + public int getMax() { + return mMaxProgress; + } + + public float getReachedBarHeight() { + return mReachedBarHeight; + } + + public float getUnreachedBarHeight() { + return mUnreachedBarHeight; + } + + public void setProgressTextSize(float textSize) { + this.mTextSize = textSize; + mTextPaint.setTextSize(mTextSize); + invalidate(); + } + + public void setProgressTextColor(int textColor) { + this.mTextColor = textColor; + mTextPaint.setColor(mTextColor); + invalidate(); + } + + public void setUnreachedBarColor(int barColor) { + this.mUnreachedBarColor = barColor; + mUnreachedBarPaint.setColor(mUnreachedBarColor); + invalidate(); + } + + public void setReachedBarColor(int progressColor) { + this.mReachedBarColor = progressColor; + mReachedBarPaint.setColor(mReachedBarColor); + invalidate(); + } + + public void setReachedBarHeight(float height) { + mReachedBarHeight = height; + } + + public void setUnreachedBarHeight(float height) { + mUnreachedBarHeight = height; + } + + public void setMax(int maxProgress) { + if (maxProgress > 0) { + this.mMaxProgress = maxProgress; + invalidate(); + } + } + + public void setSuffix(String suffix) { + if (suffix == null) { + mSuffix = ""; + } else { + mSuffix = suffix; + } + } + + public String getSuffix() { + return mSuffix; + } + + public void setPrefix(String prefix) { + if (prefix == null) + mPrefix = ""; + else { + mPrefix = prefix; + } + } + + public String getPrefix() { + return mPrefix; + } + + public void incrementProgressBy(int by) { + if (by > 0) { + setProgress(getProgress() + by); + } + } + + public void setProgress(int progress) { + if (progress <= getMax() && progress >= 0) { + this.mCurrentProgress = progress; + invalidate(); + } + } + + @Override + protected Parcelable onSaveInstanceState() { + final Bundle bundle = new Bundle(); + bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState()); + bundle.putInt(INSTANCE_TEXT_COLOR, getTextColor()); + bundle.putFloat(INSTANCE_TEXT_SIZE, getProgressTextSize()); + bundle.putFloat(INSTANCE_REACHED_BAR_HEIGHT, getReachedBarHeight()); + bundle.putFloat(INSTANCE_UNREACHED_BAR_HEIGHT, getUnreachedBarHeight()); + bundle.putInt(INSTANCE_REACHED_BAR_COLOR, getReachedBarColor()); + bundle.putInt(INSTANCE_UNREACHED_BAR_COLOR, getUnreachedBarColor()); + bundle.putInt(INSTANCE_MAX, getMax()); + bundle.putInt(INSTANCE_PROGRESS, getProgress()); + bundle.putString(INSTANCE_SUFFIX, getSuffix()); + bundle.putString(INSTANCE_PREFIX, getPrefix()); + bundle.putBoolean(INSTANCE_TEXT_VISIBILITY, getProgressTextVisibility()); + return bundle; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + final Bundle bundle = (Bundle) state; + mTextColor = bundle.getInt(INSTANCE_TEXT_COLOR); + mTextSize = bundle.getFloat(INSTANCE_TEXT_SIZE); + mReachedBarHeight = bundle.getFloat(INSTANCE_REACHED_BAR_HEIGHT); + mUnreachedBarHeight = bundle.getFloat(INSTANCE_UNREACHED_BAR_HEIGHT); + mReachedBarColor = bundle.getInt(INSTANCE_REACHED_BAR_COLOR); + mUnreachedBarColor = bundle.getInt(INSTANCE_UNREACHED_BAR_COLOR); + initializePainters(); + setMax(bundle.getInt(INSTANCE_MAX)); + setProgress(bundle.getInt(INSTANCE_PROGRESS)); + setPrefix(bundle.getString(INSTANCE_PREFIX)); + setSuffix(bundle.getString(INSTANCE_SUFFIX)); + setProgressTextVisibility(bundle.getBoolean(INSTANCE_TEXT_VISIBILITY) ? ProgressTextVisibility.Visible : ProgressTextVisibility.Invisible); + super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE)); + return; + } + super.onRestoreInstanceState(state); + } + + public float dp2px(float dp) { + final float scale = getResources().getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + public float sp2px(float sp) { + final float scale = getResources().getDisplayMetrics().scaledDensity; + return sp * scale; + } + + public void setProgressTextVisibility(ProgressTextVisibility visibility) { + mIfDrawText = visibility == ProgressTextVisibility.Visible; + invalidate(); + } + + public boolean getProgressTextVisibility() { + return mIfDrawText; + } + +} diff --git a/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/UpdateDialogActivity.kt b/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/UpdateDialogActivity.kt new file mode 100644 index 0000000..793b496 --- /dev/null +++ b/Foodify/appupdate/src/main/java/com/azhon/appupdate/view/UpdateDialogActivity.kt @@ -0,0 +1,183 @@ +package com.azhon.appupdate.view + +import android.content.Intent +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.StateListDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.azhon.appupdate.R +import com.azhon.appupdate.config.Constant +import com.azhon.appupdate.listener.OnButtonClickListener +import com.azhon.appupdate.listener.OnDownloadListenerAdapter +import com.azhon.appupdate.manager.DownloadManager +import com.azhon.appupdate.service.DownloadService +import com.azhon.appupdate.util.ApkUtil +import com.azhon.appupdate.util.DensityUtil +import com.azhon.appupdate.util.LogUtil +import java.io.File + + +/** + * ProjectName: AppUpdate + * PackageName: com.azhon.appupdate.view + * FileName: UpdateDialogActivity + * CreateDate: 2022/4/7 on 17:40 + * Desc: + * + * @author azhon + */ + +class UpdateDialogActivity : AppCompatActivity(), View.OnClickListener { + + private val install = 0x45 + private lateinit var manager: DownloadManager + private lateinit var apk: File + private lateinit var progressBar: NumberProgressBar + private lateinit var btnUpdate: Button + + companion object { + private const val TAG = "UpdateDialogActivity" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + overridePendingTransition(0, 0) + title = "" + setContentView(R.layout.dialog_update) + init() + } + + private fun init() { + val tempManager = DownloadManager.getInstance() + if (tempManager == null) { + LogUtil.e(TAG, "An exception occurred by DownloadManager=null,please check your code!") + return + } + manager = tempManager + manager.onDownloadListeners.add(listenerAdapter) + setWindowSize() + initView() + } + + private fun initView() { + val ibClose = findViewById(R.id.ib_close) + val vLine = findViewById(R.id.line) + val ivBg = findViewById(R.id.iv_bg) + val tvTitle = findViewById(R.id.tv_title) + val tvSize = findViewById(R.id.tv_size) + val tvDescription = findViewById(R.id.tv_description) + progressBar = findViewById(R.id.np_bar) + btnUpdate = findViewById(R.id.btn_update) + progressBar.visibility = View.VISIBLE + btnUpdate.tag = 0 + btnUpdate.setOnClickListener(this) + ibClose.setOnClickListener(this) + if (manager.dialogImage != -1) { + ivBg.setBackgroundResource(manager.dialogImage) + } + if (manager.dialogButtonTextColor != -1) { + btnUpdate.setTextColor(manager.dialogButtonTextColor) + } + if (manager.dialogProgressBarColor != -1) { + progressBar.reachedBarColor = manager.dialogProgressBarColor + progressBar.setProgressTextColor(manager.dialogProgressBarColor) + } + if (manager.dialogButtonColor != -1) { + val colorDrawable = GradientDrawable().apply { + setColor(manager.dialogButtonColor) + cornerRadius = DensityUtil.dip2px(this@UpdateDialogActivity, 3f) + } + val drawable = StateListDrawable().apply { + addState(intArrayOf(android.R.attr.state_pressed), colorDrawable) + addState(IntArray(0), colorDrawable) + } + btnUpdate.background = drawable + } + + if (manager.apkVersionName.isNotEmpty()) { + tvTitle.text = + String.format(resources.getString(R.string.dialog_new), manager.apkVersionName) + } + if (manager.apkSize.isNotEmpty()) { + tvSize.text = + String.format(resources.getString(R.string.dialog_new_size), manager.apkSize) + tvSize.visibility = View.VISIBLE + } + tvDescription.text = manager.apkDescription + } + + private fun setWindowSize() { + val attributes = window.attributes + attributes.width = (resources.displayMetrics.widthPixels * 0.75f).toInt() + attributes.height = WindowManager.LayoutParams.WRAP_CONTENT + attributes.gravity = Gravity.CENTER + window.attributes = attributes + } + + override fun onClick(v: View?) { + when (v?.id) { + R.id.ib_close -> { + if (!manager.forcedUpgrade) { + finish() + } + manager.onButtonClickListener?.onButtonClick(OnButtonClickListener.CANCEL) + } + R.id.btn_update -> { + if (btnUpdate.tag == install) { + ApkUtil.installApk(this, Constant.AUTHORITIES!!, apk) + return + } + if (!manager.forcedUpgrade) { + btnUpdate.isEnabled = false + btnUpdate.text = resources.getString(R.string.background_downloading) + } else { + finish() + } + manager.onButtonClickListener?.onButtonClick(OnButtonClickListener.UPDATE) + startService(Intent(this, DownloadService::class.java)) + } + } + } + + override fun onBackPressed() { + if (!manager.forcedUpgrade) return + super.onBackPressed() + } + + override fun finish() { + super.finish() + overridePendingTransition(0, 0) + } + + private val listenerAdapter: OnDownloadListenerAdapter = object : OnDownloadListenerAdapter() { + + override fun downloading(max: Int, progress: Int) { + if (max != -1 && progressBar.visibility == View.VISIBLE) { + val curr = (progress / max.toDouble() * 100.0).toInt() + progressBar.progress = curr + } else { + progressBar.visibility = View.GONE + } + } + + override fun done(apk: File) { + this@UpdateDialogActivity.apk = apk + if (!manager.forcedUpgrade) { + btnUpdate.tag = install + btnUpdate.isEnabled = true + btnUpdate.text = resources.getString(R.string.click_hint) + } + } + } + + override fun onDestroy() { + super.onDestroy() + manager.onDownloadListeners.remove(listenerAdapter) + } +} \ No newline at end of file diff --git a/Foodify/appupdate/src/main/res/drawable/bg_button.xml b/Foodify/appupdate/src/main/res/drawable/bg_button.xml new file mode 100644 index 0000000..ffb9ab3 --- /dev/null +++ b/Foodify/appupdate/src/main/res/drawable/bg_button.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Foodify/appupdate/src/main/res/drawable/bg_white_radius_6.xml b/Foodify/appupdate/src/main/res/drawable/bg_white_radius_6.xml new file mode 100644 index 0000000..1d96236 --- /dev/null +++ b/Foodify/appupdate/src/main/res/drawable/bg_white_radius_6.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/Foodify/appupdate/src/main/res/drawable/ic_dialog_close.png b/Foodify/appupdate/src/main/res/drawable/ic_dialog_close.png new file mode 100644 index 0000000..03315a3 Binary files /dev/null and b/Foodify/appupdate/src/main/res/drawable/ic_dialog_close.png differ diff --git a/Foodify/appupdate/src/main/res/drawable/ic_dialog_default.png b/Foodify/appupdate/src/main/res/drawable/ic_dialog_default.png new file mode 100644 index 0000000..2b036c8 Binary files /dev/null and b/Foodify/appupdate/src/main/res/drawable/ic_dialog_default.png differ diff --git a/Foodify/appupdate/src/main/res/layout/dialog_update.xml b/Foodify/appupdate/src/main/res/layout/dialog_update.xml new file mode 100644 index 0000000..8ab0e27 --- /dev/null +++ b/Foodify/appupdate/src/main/res/layout/dialog_update.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + +