feat: 版本号展示&Android端构建配置

This commit is contained in:
chaos-zhu
2026-05-24 22:31:38 +08:00
parent 6fa0f53384
commit 01074b73bf
9 changed files with 88 additions and 21 deletions
+5
View File
@@ -43,3 +43,8 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# Android signing — never commit keystores or their passwords.
/android/key.properties
/android/*.jks
/android/*.keystore
+33 -8
View File
@@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
@@ -5,8 +8,15 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties().apply {
val file = rootProject.file("key.properties")
if (file.exists()) {
load(FileInputStream(file))
}
}
android {
namespace = "com.example.mobile"
namespace = "io.github.chaoszhu.easynode"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -20,21 +30,36 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.mobile"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
applicationId = "io.github.chaoszhu.easynode"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
val storeFilePath = keystoreProperties["storeFile"] as String?
if (storeFilePath != null) {
storeFile = rootProject.file(storeFilePath)
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
}
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
// Fall back to the debug keystore if key.properties is missing so
// `flutter run --release` keeps working locally for contributors
// who haven't set up signing yet. Real release builds REQUIRE the
// key.properties file to be present.
signingConfig = if (keystoreProperties.isEmpty) {
signingConfigs.getByName("debug")
} else {
signingConfigs.getByName("release")
}
}
}
}
@@ -1,4 +1,4 @@
package com.example.mobile
package io.github.chaoszhu.easynode
import io.flutter.embedding.android.FlutterActivity
+4
View File
@@ -0,0 +1,4 @@
storePassword=<your-store-password>
keyPassword=<your-key-password>
keyAlias=easynode
storeFile=easynode-release.jks
+10 -6
View File
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/ui/palette.dart';
import '../../core/utils/jwt_expiry.dart';
import '../../core/utils/validators.dart';
import '../../l10n/app_localizations.dart';
import '../../state/package_info_provider.dart';
import 'auth_session.dart';
import 'login_controller.dart';
@@ -145,7 +147,6 @@ class _LoginPageState extends State<LoginPage> {
children: [
_LoginHero(
title: l.tr('app.title'),
subtitle: l.tr('app.subtitle'),
),
const SizedBox(height: 22),
_LoginFormCard(
@@ -441,15 +442,18 @@ class _LoginBottomBar extends StatelessWidget {
}
}
class _LoginHero extends StatelessWidget {
const _LoginHero({required this.title, required this.subtitle});
class _LoginHero extends ConsumerWidget {
const _LoginHero({required this.title});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final info = ref.watch(packageInfoProvider).valueOrNull;
final versionLabel = info == null
? ''
: 'v${info.version} (${info.buildNumber})';
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -476,7 +480,7 @@ class _LoginHero extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
subtitle,
versionLabel,
textAlign: TextAlign.center,
style: theme.textTheme.bodySmall?.copyWith(
color: AppPalette.softMuted,
@@ -7,6 +7,7 @@ import '../../state/auth_notifier.dart';
import '../../state/credential_list_notifier.dart';
import '../../state/host_list_notifier.dart';
import '../../state/locale_notifier.dart';
import '../../state/package_info_provider.dart';
import '../../state/plus_discount_notifier.dart';
import '../../state/plus_info_notifier.dart';
import '../../state/script_list_notifier.dart';
@@ -207,6 +208,10 @@ class SettingsTab extends ConsumerWidget {
final discount = ref.watch(plusDiscountProvider).valueOrNull ??
const PlusDiscount(discount: false, content: '');
final hasDiscount = discount.discount && discount.content.isNotEmpty;
final packageInfo = ref.watch(packageInfoProvider).valueOrNull;
final versionLabel = packageInfo == null
? ''
: 'v${packageInfo.version} (${packageInfo.buildNumber})';
return Scaffold(
backgroundColor: AppPalette.canvas,
@@ -221,6 +226,19 @@ class SettingsTab extends ConsumerWidget {
child: TabHeader(
title: l.tr('settings.title'),
actions: [
if (versionLabel.isNotEmpty)
Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(
versionLabel,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: AppPalette.softMuted,
letterSpacing: 0.2,
),
),
),
if (!plusActive)
_SettingsBellButton(
hasDiscount: hasDiscount,
@@ -0,0 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart';
/// Resolves the running app's version / build number from the platform's
/// PackageInfo so UI surfaces don't hardcode strings that drift from
/// `pubspec.yaml`. Cached for the lifetime of the process — package info
/// never changes after install.
final packageInfoProvider = FutureProvider<PackageInfo>((ref) async {
return PackageInfo.fromPlatform();
});
+5 -5
View File
@@ -486,13 +486,13 @@ packages:
source: hosted
version: "2.2.0"
package_info_plus:
dependency: transitive
dependency: "direct main"
description:
name: package_info_plus
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev"
source: hosted
version: "9.0.1"
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
@@ -966,10 +966,10 @@ packages:
dependency: transitive
description:
name: wakelock_plus
sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1
sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
version: "1.3.3"
wakelock_plus_platform_interface:
dependency: transitive
description:
+2 -1
View File
@@ -1,7 +1,7 @@
name: mobile
description: "EasyNode mobile app."
publish_to: 'none'
version: 0.1.0+1
version: 0.1.0-beta.1+2
environment:
sdk: ^3.11.5
@@ -34,6 +34,7 @@ dependencies:
video_player: ^2.9.2
chewie: ^1.8.5
url_launcher: ^6.3.1
package_info_plus: ^8.1.0
dependency_overrides:
# video_player_android 2.9.x has a known Pigeon channel-error regression on