Refactor Updater

This commit is contained in:
luvletter2333 2022-01-23 14:19:32 +08:00
parent 76ef6ac602
commit d67c5c27b9
No known key found for this signature in database
GPG Key ID: A26A8880836E1978
9 changed files with 445 additions and 329 deletions

View File

@ -1345,41 +1345,21 @@ public class SharedConfig {
}
public static boolean isAppUpdateAvailable() {
if (pendingAppUpdate == null || pendingAppUpdate.document == null || !AndroidUtilities.isStandaloneApp()) {
if (pendingAppUpdate == null || pendingAppUpdate.document == null) {
return false;
}
int currentVersion;
try {
PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0);
currentVersion = pInfo.versionCode;
} catch (Exception e) {
FileLog.e(e);
currentVersion = BuildVars.BUILD_VERSION;
}
return pendingAppUpdateBuildVersion == currentVersion;
return pendingAppUpdateBuildVersion == BuildVars.BUILD_VERSION;
}
public static boolean setNewAppVersionAvailable(TLRPC.TL_help_appUpdate update) {
String updateVersionString = null;
int versionCode = 0;
try {
PackageInfo packageInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0);
versionCode = packageInfo.versionCode;
updateVersionString = packageInfo.versionName;
} catch (Exception e) {
FileLog.e(e);
}
if (versionCode == 0) {
versionCode = BuildVars.BUILD_VERSION;
}
if (updateVersionString == null) {
updateVersionString = BuildVars.BUILD_VERSION_STRING;
}
if (update.version == null || updateVersionString.compareTo(update.version) >= 0) {
if (update == null) {
pendingAppUpdate = null;
pendingAppUpdateBuildVersion = 0;
saveConfig();
return false;
}
pendingAppUpdate = update;
pendingAppUpdateBuildVersion = versionCode;
pendingAppUpdateBuildVersion = BuildConfig.VERSION_CODE;
saveConfig();
return true;
}

View File

@ -233,7 +233,7 @@ public class BlockingUpdateView extends FrameLayout implements NotificationCente
public static boolean checkApkInstallPermissions(final Context context) {
if (Build.VERSION.SDK_INT >= 26 && !ApplicationLoader.applicationContext.getPackageManager().canRequestPackageInstalls()) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
builder.setMessage(LocaleController.getString("ApkRestricted", R.string.ApkRestricted));
builder.setPositiveButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialogInterface, i) -> {
try {

View File

@ -32,6 +32,7 @@ import org.telegram.messenger.SvgHelper;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
import tw.nekomimi.nekogram.parts.DialogTransKt;
public class UpdateAppAlertDialog extends BottomSheet {
@ -252,7 +253,7 @@ public class UpdateAppAlertDialog extends BottomSheet {
textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack));
textView.setSingleLine(true);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setText(LocaleController.getString("AppUpdate", R.string.AppUpdate));
textView.setText(LocaleController.getString("AppUpdateNekoX", R.string.AppUpdateNekoX));
linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 23, 16, 23, 0));
TextView messageTextView = new TextView(getContext());
@ -262,7 +263,16 @@ public class UpdateAppAlertDialog extends BottomSheet {
messageTextView.setLinkTextColor(Theme.getColor(Theme.key_dialogTextLink));
messageTextView.setText(LocaleController.formatString("AppUpdateVersionAndSize", R.string.AppUpdateVersionAndSize, appUpdate.version, AndroidUtilities.formatFileSize(appUpdate.document.size)));
messageTextView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
linearLayout.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 23, 0, 23, 5));
linearLayout.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 23, 5, 23, 5));
TextView translationHintTextView = new TextView(getContext());
translationHintTextView.setTextColor(Theme.getColor(Theme.key_dialogTextGray2));
translationHintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
translationHintTextView.setMovementMethod(new AndroidUtilities.LinkMovementMethodMy());
translationHintTextView.setLinkTextColor(Theme.getColor(Theme.key_dialogTextLink));
translationHintTextView.setText(LocaleController.getString("NekoXUpdateTranslationHint", R.string.NekoXUpdateTranslationHint));
translationHintTextView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
linearLayout.addView(translationHintTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 23, 0, 23, 5));
TextView changelogTextView = new TextView(getContext());
changelogTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack));
@ -277,6 +287,11 @@ public class UpdateAppAlertDialog extends BottomSheet {
changelogTextView.setText(builder);
}
changelogTextView.setGravity(Gravity.LEFT | Gravity.TOP);
changelogTextView.setOnLongClickListener(v -> {
if (TextUtils.isEmpty(appUpdate.text)) return false;
DialogTransKt.startTrans(v.getContext(), appUpdate.text);
return true;
});
linearLayout.addView(changelogTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 23, 15, 23, 0));
FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(LayoutHelper.MATCH_PARENT, AndroidUtilities.getShadowHeight(), Gravity.BOTTOM | Gravity.LEFT);

View File

@ -351,10 +351,10 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
private boolean showSetPasswordConfirm;
private int otherwiseReloginDays;
private FrameLayout updateLayout;
private AnimatorSet updateLayoutAnimator;
private RadialProgress2 updateLayoutIcon;
private TextView updateTextView;
// private FrameLayout updateLayout;
// private AnimatorSet updateLayoutAnimator;
// private RadialProgress2 updateLayoutIcon;
// private TextView updateTextView;
private DialogsActivityDelegate delegate;
@ -3415,77 +3415,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
}
if (searchString == null && initialDialogsType == 0) {
updateLayout = new FrameLayout(context) {
private Paint paint = new Paint();
private Matrix matrix = new Matrix();
private LinearGradient updateGradient;
private int lastGradientWidth;
@Override
protected void onDraw(Canvas canvas) {
if (updateGradient == null) {
return;
}
paint.setColor(0xffffffff);
paint.setShader(updateGradient);
updateGradient.setLocalMatrix(matrix);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
updateLayoutIcon.setBackgroundGradientDrawable(updateGradient);
updateLayoutIcon.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (lastGradientWidth != width) {
updateGradient = new LinearGradient(0, 0, width, 0, new int[]{0xff69BF72, 0xff53B3AD}, new float[]{0.0f, 1.0f}, Shader.TileMode.CLAMP);
lastGradientWidth = width;
}
int x = (getMeasuredWidth() - updateTextView.getMeasuredWidth()) / 2;
updateLayoutIcon.setProgressRect(x, AndroidUtilities.dp(13), x + AndroidUtilities.dp(22), AndroidUtilities.dp(13 + 22));
}
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
additionalFloatingTranslation2 = AndroidUtilities.dp(48) - translationY;
if (additionalFloatingTranslation2 < 0) {
additionalFloatingTranslation2 = 0;
}
if (!floatingHidden) {
updateFloatingButtonOffset();
}
}
};
updateLayout.setWillNotDraw(false);
updateLayout.setVisibility(View.INVISIBLE);
updateLayout.setTranslationY(AndroidUtilities.dp(48));
if (Build.VERSION.SDK_INT >= 21) {
updateLayout.setBackground(Theme.getSelectorDrawable(Theme.getColor(Theme.key_listSelector), null));
}
contentView.addView(updateLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM));
updateLayout.setOnClickListener(v -> {
if (!SharedConfig.isAppUpdateAvailable()) {
return;
}
AndroidUtilities.openForView(SharedConfig.pendingAppUpdate.document, true, getParentActivity());
});
updateLayoutIcon = new RadialProgress2(updateLayout);
updateLayoutIcon.setColors(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
updateLayoutIcon.setCircleRadius(AndroidUtilities.dp(11));
updateLayoutIcon.setAsMini();
updateLayoutIcon.setIcon(MediaActionDrawable.ICON_UPDATE, true, false);
updateTextView = new TextView(context);
updateTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
updateTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
updateTextView.setText(LocaleController.getString("AppUpdateNow", R.string.AppUpdateNow).toUpperCase());
updateTextView.setTextColor(0xffffffff);
updateTextView.setPadding(AndroidUtilities.dp(30), 0, 0, 0);
updateLayout.addView(updateTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 0, 0, 0));
// NekoX: Remove UPDATE NOW Bottom View in DialogsActivity
}
for (int a = 0; a < 2; a++) {
@ -3615,8 +3545,6 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
PrivacyUtil.postCheckAll(getParentActivity(), currentAccount);
if (new Random().nextInt(100) < 20)
UpdateUtil.postCheckFollowChannel(getParentActivity(), currentAccount);
if (!BuildVars.isFdroid && !BuildVars.isPlay && NekoXConfig.autoUpdateReleaseChannel != 0 && System.currentTimeMillis() / 1000 > NekoXConfig.nextUpdateCheck)
UIUtil.runOnIoDispatcher(() -> InternalUpdater.checkUpdate(getParentActivity(), true), 6000);
if (NekoXConfig.developerMode && !NekoXConfig.isDeveloper())
NekoXConfig.toggleDeveloperMode();
@ -3624,66 +3552,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
}
private void updateAppUpdateViews(boolean animated) {
if (updateLayout == null) {
return;
}
boolean show;
if (SharedConfig.isAppUpdateAvailable()) {
String fileName = FileLoader.getAttachFileName(SharedConfig.pendingAppUpdate.document);
File path = FileLoader.getPathToAttach(SharedConfig.pendingAppUpdate.document, true);
show = path.exists();
} else {
show = false;
}
if (show) {
if (updateLayout.getTag() != null) {
return;
}
if (updateLayoutAnimator != null) {
updateLayoutAnimator.cancel();
}
updateLayout.setVisibility(View.VISIBLE);
updateLayout.setTag(1);
if (animated) {
updateLayoutAnimator = new AnimatorSet();
updateLayoutAnimator.setDuration(180);
updateLayoutAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT);
updateLayoutAnimator.playTogether(ObjectAnimator.ofFloat(updateLayout, View.TRANSLATION_Y, 0));
updateLayoutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateLayoutAnimator = null;
}
});
updateLayoutAnimator.start();
} else {
updateLayout.setTranslationY(0);
}
} else {
if (updateLayout.getTag() == null) {
return;
}
updateLayout.setTag(null);
if (animated) {
updateLayoutAnimator = new AnimatorSet();
updateLayoutAnimator.setDuration(180);
updateLayoutAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT);
updateLayoutAnimator.playTogether(ObjectAnimator.ofFloat(updateLayout, View.TRANSLATION_Y, AndroidUtilities.dp(48)));
updateLayoutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (updateLayout.getTag() == null) {
updateLayout.setVisibility(View.INVISIBLE);
}
updateLayoutAnimator = null;
}
});
updateLayoutAnimator.start();
} else {
updateLayout.setTranslationY(AndroidUtilities.dp(48));
updateLayout.setVisibility(View.INVISIBLE);
}
}
// NekoX: Remove UPDATE NOW Bottom View in DialogsActivity
}
private void updateContextViewPosition() {
@ -6728,7 +6597,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
View databaseMigrationHint;
private void updateMenuButton(boolean animated) {
if (menuDrawable == null || updateLayout == null) {
if (menuDrawable == null) {
return;
}
int type;

View File

@ -22,7 +22,11 @@ import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Shader;
import android.location.Location;
import android.location.LocationManager;
import android.media.AudioManager;
@ -36,7 +40,9 @@ import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Base64;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MotionEvent;
@ -113,11 +119,13 @@ import org.telegram.ui.Components.AudioPlayerAlert;
import org.telegram.ui.Components.BlockingUpdateView;
import org.telegram.ui.Components.Bulletin;
import org.telegram.ui.Components.BulletinFactory;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.Easings;
import org.telegram.ui.Components.EmbedBottomSheet;
import org.telegram.ui.Components.GroupCallPip;
import org.telegram.ui.Components.JoinGroupAlert;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.MediaActionDrawable;
import org.telegram.ui.Components.PasscodeView;
import org.telegram.ui.Components.PhonebookShareAlert;
import org.telegram.ui.Components.PipRoundVideoView;
@ -133,6 +141,7 @@ import org.telegram.ui.Components.StickersAlert;
import org.telegram.ui.Components.TermsOfServiceView;
import org.telegram.ui.Components.ThemeEditorView;
import org.telegram.ui.Components.UndoView;
import org.telegram.ui.Components.UpdateAppAlertDialog;
import org.telegram.ui.Components.voip.VoIPHelper;
import org.webrtc.voiceengine.WebRtcAudioTrack;
@ -155,6 +164,7 @@ import java.util.regex.Pattern;
import cn.hutool.core.util.StrUtil;
import kotlin.Unit;
import kotlin.text.StringsKt;
import tw.nekomimi.nekogram.InternalUpdater;
import tw.nekomimi.nekogram.ui.BottomBuilder;
import tw.nekomimi.nekogram.ExternalGcm;
import tw.nekomimi.nekogram.NekoConfig;
@ -2085,6 +2095,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
} catch (Exception e) {
FileLog.e(e);
}
} else if (url.startsWith("tg:upgrade") || url.startsWith("tg://upgrade") || url.startsWith("tg:update") || url.startsWith("tg://update")) {
checkAppUpdate(true);
} else if ((url.startsWith("tg:search") || url.startsWith("tg://search"))) {
url = url.replace("tg:search", "tg://telegram.org").replace("tg://search", "tg://telegram.org");
data = Uri.parse(url);
@ -3652,6 +3664,201 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
return foundContacts;
}
private void createUpdateUI() {
if (sideMenuContainer == null) {
return;
}
updateLayout = new FrameLayout(this) {
private Paint paint = new Paint();
private Matrix matrix = new Matrix();
private LinearGradient updateGradient;
private int lastGradientWidth;
@Override
protected void onDraw(Canvas canvas) {
if (updateGradient == null) {
return;
}
paint.setColor(0xffffffff);
paint.setShader(updateGradient);
updateGradient.setLocalMatrix(matrix);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
updateLayoutIcon.setBackgroundGradientDrawable(updateGradient);
updateLayoutIcon.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (lastGradientWidth != width) {
updateGradient = new LinearGradient(0, 0, width, 0, new int[]{0xff69BF72, 0xff53B3AD}, new float[]{0.0f, 1.0f}, Shader.TileMode.CLAMP);
lastGradientWidth = width;
}
}
};
updateLayout.setWillNotDraw(false);
updateLayout.setVisibility(View.INVISIBLE);
updateLayout.setTranslationY(AndroidUtilities.dp(44));
if (Build.VERSION.SDK_INT >= 21) {
updateLayout.setBackground(Theme.getSelectorDrawable(Theme.getColor(Theme.key_listSelector), null));
}
sideMenuContainer.addView(updateLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.BOTTOM));
updateLayout.setOnClickListener(v -> {
if (!SharedConfig.isAppUpdateAvailable()) {
return;
}
if (updateLayoutIcon.getIcon() == MediaActionDrawable.ICON_DOWNLOAD) {
FileLoader.getInstance(currentAccount).loadFile(SharedConfig.pendingAppUpdate.document, "update", 1, 1);
updateAppUpdateViews(true);
} else if (updateLayoutIcon.getIcon() == MediaActionDrawable.ICON_CANCEL) {
FileLoader.getInstance(currentAccount).cancelLoadFile(SharedConfig.pendingAppUpdate.document);
updateAppUpdateViews(true);
} else {
AndroidUtilities.openForView(SharedConfig.pendingAppUpdate.document, true, this);
}
});
updateLayoutIcon = new RadialProgress2(updateLayout);
updateLayoutIcon.setColors(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
updateLayoutIcon.setProgressRect(AndroidUtilities.dp(22), AndroidUtilities.dp(11), AndroidUtilities.dp(22 + 22), AndroidUtilities.dp(11 + 22));
updateLayoutIcon.setCircleRadius(AndroidUtilities.dp(11));
updateLayoutIcon.setAsMini();
updateTextView = new SimpleTextView(this);
updateTextView.setTextSize(15);
updateTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
updateTextView.setText(LocaleController.getString("AppUpdate", R.string.AppUpdate));
updateTextView.setTextColor(0xffffffff);
updateTextView.setGravity(Gravity.LEFT);
updateLayout.addView(updateTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 74, 0, 0, 0));
updateSizeTextView = new TextView(this);
updateSizeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
updateSizeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
updateSizeTextView.setGravity(Gravity.RIGHT);
updateSizeTextView.setTextColor(0xffffffff);
updateLayout.addView(updateSizeTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 0, 17, 0));
}
private void updateAppUpdateViews(boolean animated) {
if (sideMenuContainer == null) {
return;
}
if (SharedConfig.isAppUpdateAvailable()) {
createUpdateUI();
updateSizeTextView.setText(AndroidUtilities.formatFileSize(SharedConfig.pendingAppUpdate.document.size));
String fileName = FileLoader.getAttachFileName(SharedConfig.pendingAppUpdate.document);
File path = FileLoader.getPathToAttach(SharedConfig.pendingAppUpdate.document, true);
boolean showSize;
if (path.exists()) {
updateLayoutIcon.setIcon(MediaActionDrawable.ICON_UPDATE, true, animated);
updateTextView.setText(LocaleController.getString("AppUpdateNow", R.string.AppUpdateNow));
showSize = false;
} else {
if (FileLoader.getInstance(currentAccount).isLoadingFile(fileName)) {
updateLayoutIcon.setIcon(MediaActionDrawable.ICON_CANCEL, true, animated);
Float p = ImageLoader.getInstance().getFileProgress(fileName);
updateTextView.setText(LocaleController.formatString("AppUpdateDownloading", R.string.AppUpdateDownloading, (int) ((p != null ? p : 0.0f) * 100)));
showSize = false;
} else {
updateLayoutIcon.setIcon(MediaActionDrawable.ICON_DOWNLOAD, true, animated);
updateTextView.setText(LocaleController.getString("AppUpdate", R.string.AppUpdate));
showSize = true;
}
}
if (showSize) {
if (updateSizeTextView.getTag() != null) {
if (animated) {
updateSizeTextView.setTag(null);
updateSizeTextView.animate().alpha(1.0f).scaleX(1.0f).scaleY(1.0f).setDuration(180).start();
} else {
updateSizeTextView.setAlpha(1.0f);
updateSizeTextView.setScaleX(1.0f);
updateSizeTextView.setScaleY(1.0f);
}
}
} else {
if (updateSizeTextView.getTag() == null) {
if (animated) {
updateSizeTextView.setTag(1);
updateSizeTextView.animate().alpha(0.0f).scaleX(0.0f).scaleY(0.0f).setDuration(180).start();
} else {
updateSizeTextView.setAlpha(0.0f);
updateSizeTextView.setScaleX(0.0f);
updateSizeTextView.setScaleY(0.0f);
}
}
}
if (updateLayout.getTag() != null) {
return;
}
updateLayout.setVisibility(View.VISIBLE);
updateLayout.setTag(1);
if (animated) {
updateLayout.animate().translationY(0).setInterpolator(CubicBezierInterpolator.EASE_OUT).setListener(null).setDuration(180).start();
} else {
updateLayout.setTranslationY(0);
}
sideMenu.setPadding(0, 0, 0, AndroidUtilities.dp(44));
} else {
if (updateLayout == null || updateLayout.getTag() == null) {
return;
}
updateLayout.setTag(null);
if (animated) {
updateLayout.animate().translationY(AndroidUtilities.dp(44)).setInterpolator(CubicBezierInterpolator.EASE_OUT).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (updateLayout.getTag() == null) {
updateLayout.setVisibility(View.INVISIBLE);
}
}
}).setDuration(180).start();
} else {
updateLayout.setTranslationY(AndroidUtilities.dp(44));
updateLayout.setVisibility(View.INVISIBLE);
}
sideMenu.setPadding(0, 0, 0, 0);
}
}
public void checkAppUpdate(boolean force) {
if (BuildVars.isFdroid || BuildVars.isPlay) return;
if (NekoXConfig.autoUpdateReleaseChannel == 0) return;
if (!force && System.currentTimeMillis() < SharedConfig.lastUpdateCheckTime + 1000L * 60 * 60) return;
SharedConfig.lastUpdateCheckTime = System.currentTimeMillis();
SharedConfig.saveConfig();
FileLog.d("checking update");
final int accountNum = currentAccount;
InternalUpdater.checkUpdate((res, error) -> AndroidUtilities.runOnUIThread(() -> {
if (res != null) {
SharedConfig.setNewAppVersionAvailable(res);
if (res.can_not_skip) {
showUpdateActivity(accountNum, res, false);
} else {
drawerLayoutAdapter.notifyDataSetChanged();
try {
(new UpdateAppAlertDialog(LaunchActivity.this, res, accountNum)).show();
} catch (Exception e) {
FileLog.e(e);
}
}
} else {
if (force) {
if (error)
Toast.makeText(LaunchActivity.this, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred), Toast.LENGTH_SHORT).show();
else
Toast.makeText(LaunchActivity.this, LocaleController.getString("VersionUpdateNoUpdate", R.string.VersionUpdateNoUpdate), Toast.LENGTH_SHORT).show();
}
SharedConfig.setNewAppVersionAvailable(null);
drawerLayoutAdapter.notifyDataSetChanged();
}
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.appUpdateAvailable);
}));
}
public AlertDialog showAlertDialog(AlertDialog.Builder builder) {
try {
if (visibleDialog != null) {
@ -4282,6 +4489,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
} else if (SharedConfig.pendingAppUpdate != null && SharedConfig.pendingAppUpdate.can_not_skip) {
showUpdateActivity(UserConfig.selectedAccount, SharedConfig.pendingAppUpdate, true);
}
checkAppUpdate(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ApplicationLoader.canDrawOverlays = Settings.canDrawOverlays(this);
@ -4597,6 +4805,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
if (SharedConfig.isAppUpdateAvailable()) {
String name = FileLoader.getAttachFileName(SharedConfig.pendingAppUpdate.document);
if (name.equals(path)) {
updateAppUpdateViews(true);
}
}
if (loadingThemeFileName != null) {
@ -4667,6 +4876,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
if (SharedConfig.isAppUpdateAvailable()) {
String name = FileLoader.getAttachFileName(SharedConfig.pendingAppUpdate.document);
if (name.equals(path)) {
updateAppUpdateViews(true);
}
}
} else if (id == NotificationCenter.screenStateChanged) {
@ -4741,6 +4951,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
}
}
} else if (id == NotificationCenter.appUpdateAvailable) {
updateAppUpdateViews(mainFragmentsStack.size() == 1);
}
}

View File

@ -3007,7 +3007,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
if (!BuildVars.isFdroid && !BuildVars.isPlay) {
builder.addItem(LocaleController.getString("CheckUpdate", R.string.CheckUpdate), R.drawable.baseline_search_24, (it) -> {
UIUtil.runOnIoDispatcher(() -> InternalUpdater.checkUpdate(getParentActivity(), false));
Browser.openUrl(context, "tg://update");
return Unit.INSTANCE;
});
@ -3022,6 +3022,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
case 2:
currentChannel += LocaleController.getString("AutoCheckUpdateRc", R.string.AutoCheckUpdateRc);
break;
case 3:
currentChannel += LocaleController.getString("AutoCheckUpdatePreview", R.string.AutoCheckUpdatePreview);
break;
}
builder.addItem(LocaleController.getString("AutoCheckUpdateSwitch", R.string.AutoCheckUpdateSwitch) + currentChannel, R.drawable.update_black_24, (it) -> {
@ -3042,6 +3045,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
switchBuilder.doRadioCheck(radioButtonCell);
return Unit.INSTANCE;
});
switchBuilder.addRadioItem(LocaleController.getString("AutoCheckUpdatePreview", R.string.AutoCheckUpdatePreview), NekoXConfig.autoUpdateReleaseChannel == 3, (radioButtonCell) -> {
NekoXConfig.setAutoUpdateReleaseChannel(3);
switchBuilder.doRadioCheck(radioButtonCell);
return Unit.INSTANCE;
});
showDialog(switchBuilder.create());
return Unit.INSTANCE;
});

View File

@ -1,186 +1,216 @@
package tw.nekomimi.nekogram;
import static org.telegram.ui.Components.BlockingUpdateView.checkApkInstallPermissions;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.widget.Toast;
import androidx.core.content.FileProvider;
import com.google.gson.Gson;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.browser.Browser;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.messenger.UserConfig;
import org.telegram.tgnet.TLRPC;
import org.webrtc.EglBase;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import cn.hutool.http.HttpRequest;
import tw.nekomimi.nekogram.utils.FileUtil;
//TODO use UpdateAppAlertDialog / BlockingUpdateView?
public class InternalUpdater {
private static final String API_URL_RELEASE = "https://api.github.com/repos/NekoX-Dev/NekoX/releases?per_page=10";
private static class ReleaseMetadata {
String name;
String body;
String published_at;
String html_url;
ApkMetadata[] assets;
}
static final int UPDATE_METADATA_START_FROM = 0;
static final int MAX_READ_COUNT = 20;
static final long CHANNEL_METADATA_ID = 1359638116;
static final String CHANNEL_METADATA_NAME = "nekox_update_metadata";
static final long CHANNEL_APKS_ID = 1137038259;
static final String CHANNEL_APKS_NAME = "NekoXApks";
private static class ApkMetadata {
String name;
String browser_download_url;
}
private static class GithubApiContents {
String content;
}
// as a base64 encoded json
private static class NekoXReleaseNote {
NekoXAPK[] apks;
}
private static class NekoXAPK {
String name;
String sha1;
String[] urls; // https://t.me/xxx or bdex://xxx, bdex removed
}
private static ApkMetadata matchBuild(ApkMetadata[] apks) {
String target = BuildConfig.FLAVOR + "-" + FileUtil.getAbi() + "-" + BuildConfig.BUILD_TYPE + ".apk";
FileLog.e(target);
for (ApkMetadata apk : apks) {
if (apk.name.contains(target))
return apk;
}
return null;
}
public static void checkUpdate(Context ctx, boolean isAutoCheck) {
if (BuildVars.isFdroid)
return;
//cleanup
File f = new File(ApplicationLoader.getDataDirFixed(), "cache/new.apk");
if (f.exists()) f.delete();
try {
NekoXConfig.setNextUpdateCheck(System.currentTimeMillis() / 1000 + 24 * 3600);
//TODO update URL when api.github.com get banned.
String ret = HttpRequest.get(API_URL_RELEASE).header("accept", "application/vnd.github.v3+json").execute().body();
ReleaseMetadata[] releases = new Gson().fromJson(ret, ReleaseMetadata[].class);
ReleaseMetadata release = null;
// Not now.
String releaseChannel = "stable";
switch (NekoXConfig.autoUpdateReleaseChannel) {
case 2:
releaseChannel = "rc";
break;
case 3:
releaseChannel = "preview";
break;
static void retrieveUpdateMetadata(retrieveUpdateMetadataCallback callback) {
final int localVersionCode = BuildVars.BUILD_VERSION;
AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount);
TLRPC.TL_messages_getHistory req = new TLRPC.TL_messages_getHistory();
req.peer = accountInstance.getMessagesController().getInputPeer(-CHANNEL_METADATA_ID);
req.offset_id = 0;
req.limit = MAX_READ_COUNT;
Runnable sendReq = () -> accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> {
if (error != null) {
FileLog.e("Error when retrieving update metadata from channel " + error);
callback.apply(null, true);
return;
}
for (ReleaseMetadata rel : releases) {
if (rel.name.equals("v" + BuildConfig.VERSION_NAME))
break;
if (rel.name.contains("rc") && NekoXConfig.autoUpdateReleaseChannel < 2 || rel.name.contains("preview") && NekoXConfig.autoUpdateReleaseChannel < 3)
TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
List<UpdateMetadata> metas = new ArrayList<>();
for (TLRPC.Message message : res.messages) {
if (!(message instanceof TLRPC.TL_message)) continue;
if (!message.message.startsWith("v")) continue;
String[] split = message.message.split(",");
if (split.length < 4) continue;
UpdateMetadata metaData = new UpdateMetadata(message.id, split);
metas.add(metaData);
}
Collections.sort(metas, (o1, o2) -> o2.versionCode - o1.versionCode); // versionCode Desc
UpdateMetadata found = null;
for (UpdateMetadata metaData : metas) {
if (metaData.versionCode <= localVersionCode) break;
if (NekoXConfig.autoUpdateReleaseChannel < 3 && metaData.versionName.contains("preview"))
continue;
release = rel;
if (NekoXConfig.autoUpdateReleaseChannel < 2 && metaData.versionName.contains("rc"))
continue;
found = metaData;
break;
}
if (release == null) {
FileLog.d("no update");
if (!isAutoCheck)
AndroidUtilities.runOnUIThread(() -> Toast.makeText(ctx, LocaleController.getString("VersionUpdateNoUpdate", R.string.VersionUpdateNoUpdate), Toast.LENGTH_SHORT).show());
return;
} else if (release.name.equals(NekoXConfig.ignoredUpdateTag) && isAutoCheck) {
FileLog.d("ignored tag " + release.name);
if (found != null) {
for (TLRPC.Message message : res.messages) {
if (!(message instanceof TLRPC.TL_message)) continue;
if (message.id == found.UpdateLogMessageID) {
found.updateLog = message.message;
found.updateLogEntities = message.entities;
break;
}
}
}
if (found == null) {
FileLog.d("Cannot find Update Metadata");
callback.apply(null, false);
return;
}
// match release apk urls
final ApkMetadata apk = matchBuild(release.assets);
// match apk urls. these can be empty.
ReleaseMetadata finalRelease = release;
AndroidUtilities.runOnUIThread(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(LocaleController.getString("VersionUpdateTitle", R.string.VersionUpdateTitle));
String message = null;
try {
message = finalRelease.name + " " + LocaleController.formatDateChat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(finalRelease.published_at).getTime() / 1000) + "\n\n";
} catch (Exception e) {
FileLog.e(e);
FileLog.w("Found Update Metadata " + found.versionName + " " + found.versionCode);
callback.apply(found, false);
});
if (req.peer.access_hash != 0) sendReq.run();
else {
TLRPC.TL_contacts_resolveUsername resolve = new TLRPC.TL_contacts_resolveUsername();
resolve.username = CHANNEL_METADATA_NAME;
accountInstance.getConnectionsManager().sendRequest(resolve, (response1, error1) -> {
if (error1 != null) {
FileLog.e("Error when checking update, unable to resolve metadata channel " + error1.text);
callback.apply(null, true);
return;
}
if (apk == null)
message += LocaleController.getString("VersionUpdateVariantNotMatch", R.string.VersionUpdateVariantNotMatch);
else
message += apk.name.replace(".apk", "");
builder.setMessage(message);
builder.setPositiveButton(LocaleController.getString("VersionUpdateConfirm", R.string.VersionUpdateConfirm), (dialog, which) -> {
Browser.openUrl(ctx, apk.browser_download_url);
});
builder.setNeutralButton(LocaleController.getString("VersionUpdateIgnore", R.string.VersionUpdateIgnore), (dialog, which) -> NekoXConfig.setIgnoredUpdateTag(finalRelease.name));
builder.setNegativeButton(LocaleController.getString("VersionUpdateNotNow", R.string.VersionUpdateNotNow), (dialog, which) -> NekoXConfig.setNextUpdateCheck(System.currentTimeMillis() / 1000 + 3 * 24 * 3600));
builder.show();
if (!(response1 instanceof TLRPC.TL_contacts_resolvedPeer)) {
FileLog.e("Error when checking update, unable to resolve metadata channel, unexpected responseType " + response1.getClass().getName());
callback.apply(null, true);
return;
}
TLRPC.TL_contacts_resolvedPeer resolvedPeer = (TLRPC.TL_contacts_resolvedPeer) response1;
accountInstance.getMessagesController().putUsers(resolvedPeer.users, false);
accountInstance.getMessagesController().putChats(resolvedPeer.chats, false);
accountInstance.getMessagesStorage().putUsersAndChats(resolvedPeer.users, resolvedPeer.chats, false, true);
if ((resolvedPeer.chats == null || resolvedPeer.chats.size() == 0)) {
FileLog.e("Error when checking update, unable to resolve metadata channel, unexpected resolvedChat ");
callback.apply(null, true);
return;
}
req.peer = new TLRPC.TL_inputPeerChannel();
req.peer.channel_id = resolvedPeer.chats.get(0).id;
req.peer.access_hash = resolvedPeer.chats.get(0).access_hash;
sendReq.run();
});
} catch (Exception e) {
FileLog.e(e);
if (!isAutoCheck)
AndroidUtilities.runOnUIThread(() -> Toast.makeText(ctx, "An exception occurred during checking updates.", Toast.LENGTH_SHORT).show());
}
}
public static boolean openApkInstall(Activity activity, File f) {
if (!checkApkInstallPermissions(activity)) {
return false;
}
boolean exists = false;
try {
if (exists = f.exists()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= 24) {
intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(f), "application/vnd.android.package-archive");
}
try {
activity.startActivityForResult(intent, 500);
} catch (Exception e) {
FileLog.e(e);
}
public static void checkUpdate(checkUpdateCallback callback) {
AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount);
retrieveUpdateMetadata((metadata, err) -> {
if (metadata == null) {
callback.apply(null, err);
return;
}
} catch (Exception e) {
FileLog.e(e);
TLRPC.TL_messages_getHistory req = new TLRPC.TL_messages_getHistory();
req.peer = accountInstance.getMessagesController().getInputPeer(-CHANNEL_APKS_ID);
req.offset_id = metadata.apkChannelMessageID;
req.limit = MAX_READ_COUNT;
Runnable sendReq = () -> accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> {
if (error != null) {
FileLog.e("Error when getting update document " + error.text);
callback.apply(null, true);
return;
}
TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
FileLog.d("Retrieve update messages, size:" + res.messages.size());
final String target = BuildConfig.FLAVOR + "-" + FileUtil.getAbi() + "-" + ("debug".equals(BuildConfig.BUILD_TYPE) ? "release" : BuildConfig.BUILD_TYPE) + ".apk";
for (int i = 0; i < res.messages.size(); i++) {
if (res.messages.get(i).media == null) continue;
TLRPC.Document apkDocument = res.messages.get(i).media.document;
String fileName = apkDocument.attributes.size() == 0 ? "" : apkDocument.attributes.get(0).file_name;
if (!(fileName.contains(target) && fileName.contains(metadata.versionName) ))
continue;
TLRPC.TL_help_appUpdate update = new TLRPC.TL_help_appUpdate();
update.version = metadata.versionName;
update.document = apkDocument;
update.can_not_skip = false;
update.flags |= 2;
if (metadata.updateLog != null) {
update.text = metadata.updateLog;
update.entities = metadata.updateLogEntities;
}
callback.apply(update, false);
return;
}
callback.apply(null, false);
});
if (req.peer.access_hash != 0) sendReq.run();
else {
TLRPC.TL_contacts_resolveUsername resolve = new TLRPC.TL_contacts_resolveUsername();
resolve.username = CHANNEL_APKS_NAME;
accountInstance.getConnectionsManager().sendRequest(resolve, (response1, error1) -> {
if (error1 != null) {
FileLog.e("Error when checking update, unable to resolve metadata channel " + error1);
callback.apply(null, true);
return;
}
if (!(response1 instanceof TLRPC.TL_contacts_resolvedPeer)) {
FileLog.e("Error when checking update, unable to resolve metadata channel, unexpected responseType " + response1.getClass().getName());
callback.apply(null, true);
return;
}
TLRPC.TL_contacts_resolvedPeer resolvedPeer = (TLRPC.TL_contacts_resolvedPeer) response1;
accountInstance.getMessagesController().putUsers(resolvedPeer.users, false);
accountInstance.getMessagesController().putChats(resolvedPeer.chats, false);
accountInstance.getMessagesStorage().putUsersAndChats(resolvedPeer.users, resolvedPeer.chats, false, true);
if ((resolvedPeer.chats == null || resolvedPeer.chats.size() == 0)) {
FileLog.e("Error when checking update, unable to resolve metadata channel, unexpected resolvedChat ");
callback.apply(null, true);
return;
}
req.peer = new TLRPC.TL_inputPeerChannel();
req.peer.channel_id = resolvedPeer.chats.get(0).id;
req.peer.access_hash = resolvedPeer.chats.get(0).access_hash;
sendReq.run();
});
}
});
}
public interface retrieveUpdateMetadataCallback {
void apply(UpdateMetadata metadata, boolean error);
}
public interface checkUpdateCallback {
void apply(TLRPC.TL_help_appUpdate resp, boolean error);
}
static class UpdateMetadata {
int messageID;
String versionName;
int versionCode;
int apkChannelMessageID;
int UpdateLogMessageID;
String updateLog = null;
ArrayList<TLRPC.MessageEntity> updateLogEntities = null;
UpdateMetadata(int messageID, String[] split) {
this.messageID = messageID;
versionName = split[0];
versionCode = Integer.parseInt(split[1]);
apkChannelMessageID = Integer.parseInt(split[2]);
UpdateLogMessageID = Integer.parseInt(split[3]);
}
return exists;
}
}

View File

@ -82,7 +82,7 @@ public class NekoXConfig {
public static int autoUpdateReleaseChannel = preferences.getInt("autoUpdateReleaseChannel", 2);
public static String ignoredUpdateTag = preferences.getString("ignoredUpdateTag", "");
public static long nextUpdateCheck = preferences.getLong("nextUpdateCheckTimestamp", 0);
// public static long nextUpdateCheck = preferences.getLong("nextUpdateCheckTimestamp", 0);
// public static int customApi = preferences.getInt("custom_api", 0);
// public static int customAppId = preferences.getInt("custom_app_id", 0);
@ -135,9 +135,9 @@ public class NekoXConfig {
return BuildConfig.APP_HASH;
}
public static void setNextUpdateCheck(long timestamp) {
preferences.edit().putLong("nextUpdateCheckTimestamp", nextUpdateCheck = timestamp).apply();
}
// public static void setNextUpdateCheck(long timestamp) {
// preferences.edit().putLong("nextUpdateCheckTimestamp", nextUpdateCheck = timestamp).apply();
// }
public static boolean isDeveloper() {
if (hasDeveloper != null)

View File

@ -247,6 +247,7 @@
<string name="AutoCheckUpdateSwitch">Automatic Update</string>
<string name="AutoCheckUpdateStable">Stable</string>
<string name="AutoCheckUpdateRc">Release Candidate</string>
<string name="AutoCheckUpdatePreview">Preview (Unstable)</string>
<string name="unreadBadgeOnBackButton">Unread badge on back button</string>
<string name="customPublicProxyIP">Set IP of public proxy</string>
<string name="customPublicProxyIPNotice">In some areas, specifying the IP of public proxy CDN(eg. Cloudflare) can speed up access.</string>
@ -263,4 +264,6 @@
<string name="rememberAllBackMessages">Remember all clicked replies</string>
<string name="DisableInstantCamera">Disable instant camera</string>
<string name="showSeconds">Show timestamp in seconds</string>
<string name="AppUpdateNekoX">Update Nekogram X</string>
<string name="NekoXUpdateTranslationHint">Press to translate the change log</string>
</resources>