Skip to content

Commit a86ac76

Browse files
committed
feat: F-Droidビルドにおける自己アップデート機能の無効化と関連設定の追加
1 parent f8d022d commit a86ac76

File tree

5 files changed

+132
-33
lines changed

5 files changed

+132
-33
lines changed

README.md

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,81 @@ AtCoderの精進をスマホでも。
5252

5353
ビルド済みのバイナリ(APKなど)は[GitHubリリーズページ](https://github.com/Shojin-App/shojin_app/releases)からダウンロードできます。これが最も簡単な開始方法です。
5454

55-
### F-Droid対応版
55+
### F-Droid 対応版 / Reproducible Build
5656

57-
このアプリはF-Droidポリシーに完全準拠したビルドが可能です。F-Droid対応版では以下の機能が制限されます:
57+
このアプリは F-Droid Inclusion Policy に準拠したビルドが可能です。F-Droid フレーバー (`fdroid`) では以下が強制されます:
5858

59-
- **自己アップデート機能**: 無効化(F-Droidが管理)
60-
- **オンラインフォント取得**: 無効化(システムフォント使用)
61-
- **外部APKダウンロード**: 完全に無効
59+
| 機能 | 通常ビルド | F-Droid ビルド |
60+
|------|------------|----------------|
61+
| 自己アップデート (アプリ内ダウンロード & APKインストール) | 有効 (ユーザー設定可) | 完全無効 (コードパス停止) |
62+
| 起動時自動チェック UI | 表示 | 非表示 |
63+
| オンラインフォント (Google Fonts) | 利用可 | 無効 (同梱/システムフォント) |
64+
| 外部APKダウンロード | 有効 | 無効 |
65+
| Git 依存 (動的取得) | なし (除去済) | なし |
6266

63-
#### F-Droid対応ビルド手順
67+
自己アップデート関連クラス (`AutoUpdateManager`, `UpdateManager`) は F-Droid ビルドでは早期 return するノーオペ実装になり、不用意に APK を取得・実行しません。
68+
69+
#### ビルド手順 (F-Droid フレーバー)
6470

6571
```bash
66-
# F-Droid互換ビルドの実行
72+
# 推奨: スクリプトで再現性確保
6773
./build_fdroid.sh
6874

69-
# または手動ビルド
75+
# 手動:
7076
flutter build apk \
71-
--dart-define=FDROID_BUILD=true \
72-
--dart-define=ENABLE_SELF_UPDATE=false \
73-
--dart-define=ENABLE_ONLINE_FONTS=false \
74-
--flavor=fdroid \
75-
--release
77+
--dart-define=FDROID_BUILD=true \
78+
--dart-define=ENABLE_SELF_UPDATE=false \
79+
--dart-define=ENABLE_ONLINE_FONTS=false \
80+
--flavor=fdroid \
81+
--release
82+
```
83+
84+
#### 確認ポイント (F-Droid 申請前チェックリスト)
85+
- [ ] `git tag vX.Y.Z``pubspec.yaml``version` と一致
86+
- [ ] 起動後「設定 > 更新設定」セクションに自己更新 UI が表示されない
87+
- [ ] ネットワークトラフィックが AtCoder / コード実行 API / 静的リソース以外へ行かない
88+
- [ ] `flutter analyze``flutter test` が成功
89+
- [ ] `FONT_LICENSES.md` / LICENSE 表示画面から辿れる
90+
91+
#### メタデータ作成例 (参考)
92+
F-Droid リポジトリ (fdroiddata) 用 `metadata/io.github.shojinapp.kyopro.yml` の骨子:
93+
```yaml
94+
Categories: Development
95+
License: GPL-3.0-only
96+
SourceCode: https://github.com/Shojin-App/shojin_app
97+
IssueTracker: https://github.com/Shojin-App/shojin_app/issues
98+
Changelog: https://github.com/Shojin-App/shojin_app/releases
99+
AutoName: Shojin App
100+
RepoType: git
101+
Repo: https://github.com/Shojin-App/shojin_app.git
102+
Builds:
103+
- versionName: 1.0.0
104+
versionCode: 100
105+
commit: v1.0.0
106+
gradle:
107+
- fdroid
108+
output: app-fdroid-release.apk
109+
srclibs:
110+
- flutter@stable
111+
prebuild: |
112+
sed -i 's/publish_to: .*/publish_to: none/' pubspec.yaml
113+
build: |
114+
flutter pub get
115+
flutter build apk --dart-define=FDROID_BUILD=true --dart-define=ENABLE_SELF_UPDATE=false --dart-define=ENABLE_ONLINE_FONTS=false --flavor=fdroid --release
76116
```
77117
78-
**注意**: 実際のF-Droid提出には、Git依存関係の除去またはベンダリングが必要です。詳細は `pubspec_fdroid.yaml``FONT_LICENSES.md` を参照してください。
118+
#### ライセンス/資産
119+
- ハックジェンフォント (HackGen) : OFL 1.1 (`FONT_LICENSES.md`)
120+
- その他依存関係: アプリ内「ライセンス」画面でオフライン参照可
121+
122+
#### セキュリティ指針
123+
- 外部実行バイナリをダウンロード・実行しない
124+
- 自己更新コードはビルドフラグ下で死コード化 (早期 no-op)
125+
- 追跡/広告/分析用ライブラリを含まない
126+
127+
> F-Droid 寄稿時の最終チェックに役立つ簡易スクリプトは `build_fdroid.sh` を参照してください。
128+
129+
**注意**: メタデータ例は参考であり、実際に提出する際は最新ポリシー・バージョンに合わせて調整してください。
79130

80131
### ソースからビルド
81132

@@ -154,3 +205,5 @@ https://github.com/inotia00/revanced-manager
154205

155206
本プロジェクトおよびその内容は、AtCoder株式会社及びその関連会社とは一切関係がなく、資金提供、承認、支持、またはその他いかなる形での関連もありません。
156207
本プロジェクトで使用されている商標、サービスマーク、商号、またはその他の知的財産権は、それぞれの所有者に帰属します。
208+
209+
GitHub ロゴ, YouTube ロゴ, X(Twitter) ロゴ は各社の登録商標/商標です。これらのロゴはリンク誘導/識別目的のみで使用されており、本アプリによる公式な提携・後援・保証を意味しません。

lib/main.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import 'package:dynamic_color/dynamic_color.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter/services.dart';
77
import 'package:flutter_localizations/flutter_localizations.dart'; // 追加
8-
import 'package:google_fonts/google_fonts.dart';
98
import 'package:provider/provider.dart';
109

1110
import 'config/build_config.dart'; // Add build configuration
@@ -26,6 +25,16 @@ void main() async {
2625
// Flutter Engineの初期化を保証
2726
WidgetsFlutterBinding.ensureInitialized();
2827

28+
// F-Droid ビルド安全性アサート: 自己アップデートが無効であること
29+
assert(() {
30+
if (BuildConfig.isF_DroidBuild && BuildConfig.enableSelfUpdate) {
31+
throw StateError(
32+
'FDROID_BUILD=true なのに enableSelfUpdate が true です。ビルドフラグ/defines を再確認してください。',
33+
);
34+
}
35+
return true;
36+
}());
37+
2938
// NotificationServiceの初期化と権限リクエスト
3039
final notificationService = NotificationService();
3140
await notificationService.initialize();

lib/screens/settings_screen.dart

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -471,21 +471,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
471471
title: '更新設定',
472472
icon: Icons.system_update_alt,
473473
children: [
474-
_HapticSwitchListTile(
475-
title: 'アプリ起動時に自動で更新を確認',
476-
value: _autoUpdateCheckEnabled,
477-
onChanged: _setAutoUpdatePreference,
478-
icon: Icons.sync_outlined,
479-
),
480-
_HapticSwitchListTile(
481-
title: 'アップデート通知を表示',
482-
subtitle: '新しいバージョンが利用可能な時に通知を表示',
483-
value: _showUpdateDialog,
484-
onChanged: _setShowUpdateDialog,
485-
icon: Icons.notifications_outlined,
486-
),
487-
// Manual update section - hidden for F-Droid builds
488474
if (BuildConfig.enableSelfUpdate) ...[
475+
_HapticSwitchListTile(
476+
title: 'アプリ起動時に自動で更新を確認',
477+
value: _autoUpdateCheckEnabled,
478+
onChanged: _setAutoUpdatePreference,
479+
icon: Icons.sync_outlined,
480+
),
481+
_HapticSwitchListTile(
482+
title: 'アップデート通知を表示',
483+
subtitle: '新しいバージョンが利用可能な時に通知を表示',
484+
value: _showUpdateDialog,
485+
onChanged: _setShowUpdateDialog,
486+
icon: Icons.notifications_outlined,
487+
),
489488
Padding(
490489
padding: const EdgeInsets.symmetric(
491490
horizontal: 20.0,
@@ -522,6 +521,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
522521
],
523522
),
524523
),
524+
] else ...[
525+
Padding(
526+
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
527+
child: Row(
528+
crossAxisAlignment: CrossAxisAlignment.start,
529+
children: [
530+
const Icon(Icons.info_outline, color: Colors.grey),
531+
const SizedBox(width: 12),
532+
Expanded(
533+
child: Text(
534+
'このビルドではアプリ内自己アップデート機能は無効化されています。最新バージョンは公式GitHubリリースまたはF-Droidリポジトリ経由で入手してください。',
535+
style: AppFonts.notoSansJp(fontSize: 14),
536+
),
537+
),
538+
],
539+
),
540+
),
525541
],
526542
],
527543
);

lib/services/auto_update_manager.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,21 @@ class AutoUpdateManager {
1818

1919
// Check if auto update is enabled
2020
Future<bool> isAutoUpdateEnabled() async {
21-
if (!kEnableSelfUpdate) return false;
21+
if (!kEnableSelfUpdate) return false; // Hard off for FDroid builds
2222
final prefs = await SharedPreferences.getInstance();
2323
return prefs.getBool(_autoUpdateKey) ?? true;
2424
}
2525

2626
// Set auto update preference
2727
Future<void> setAutoUpdateEnabled(bool enabled) async {
28+
if (!kEnableSelfUpdate) return; // no-op in FDroid
2829
final prefs = await SharedPreferences.getInstance();
2930
await prefs.setBool(_autoUpdateKey, enabled);
3031
}
3132

3233
// Get last update check timestamp
3334
Future<DateTime?> getLastUpdateCheck() async {
35+
if (!kEnableSelfUpdate) return null; // no tracking when disabled
3436
final prefs = await SharedPreferences.getInstance();
3537
final timestamp = prefs.getInt(_lastUpdateCheckKey);
3638
return timestamp != null
@@ -40,24 +42,28 @@ class AutoUpdateManager {
4042

4143
// Set last update check timestamp
4244
Future<void> setLastUpdateCheck(DateTime dateTime) async {
45+
if (!kEnableSelfUpdate) return; // no-op
4346
final prefs = await SharedPreferences.getInstance();
4447
await prefs.setInt(_lastUpdateCheckKey, dateTime.millisecondsSinceEpoch);
4548
}
4649

4750
// Get skipped version
4851
Future<String?> getSkippedVersion() async {
52+
if (!kEnableSelfUpdate) return null;
4953
final prefs = await SharedPreferences.getInstance();
5054
return prefs.getString(_skippedVersionKey);
5155
}
5256

5357
// Set skipped version
5458
Future<void> setSkippedVersion(String version) async {
59+
if (!kEnableSelfUpdate) return; // no-op
5560
final prefs = await SharedPreferences.getInstance();
5661
await prefs.setString(_skippedVersionKey, version);
5762
}
5863

5964
// Check if should check for updates (based on time interval)
6065
Future<bool> shouldCheckForUpdates() async {
66+
if (!kEnableSelfUpdate) return false;
6167
if (!await isAutoUpdateEnabled()) return false;
6268

6369
final lastCheck = await getLastUpdateCheck();
@@ -75,7 +81,9 @@ class AutoUpdateManager {
7581
String repo = 'Shojin_App',
7682
}) async {
7783
if (!kEnableSelfUpdate) {
78-
debugPrint('[FDROID] Self-update disabled by build flag');
84+
debugPrint(
85+
'[FDROID] Self-update disabled by build flag (startup check skipped)',
86+
);
7987
return;
8088
}
8189
if (!await shouldCheckForUpdates()) {
@@ -127,6 +135,7 @@ class AutoUpdateManager {
127135
BuildContext context,
128136
EnhancedAppUpdateInfo updateInfo,
129137
) {
138+
if (!kEnableSelfUpdate) return; // safety
130139
if (!context.mounted) return;
131140

132141
// Show as a SnackBar first, then dialog on tap
@@ -154,6 +163,7 @@ class AutoUpdateManager {
154163
BuildContext context,
155164
EnhancedAppUpdateInfo updateInfo,
156165
) {
166+
if (!kEnableSelfUpdate) return; // safety
157167
if (!context.mounted) return;
158168

159169
showDialog(
@@ -177,6 +187,7 @@ class AutoUpdateManager {
177187
BuildContext context,
178188
EnhancedAppUpdateInfo updateInfo,
179189
) {
190+
if (!kEnableSelfUpdate) return; // safety
180191
if (!context.mounted) return;
181192

182193
showDialog(
@@ -214,6 +225,10 @@ class AutoUpdateManager {
214225
String owner = 'yuubinnkyoku',
215226
String repo = 'Shojin_App',
216227
}) async {
228+
if (!kEnableSelfUpdate) {
229+
debugPrint('[FDROID] Manual update check suppressed');
230+
return null;
231+
}
217232
try {
218233
debugPrint('=== 手動アップデートチェック開始 ===');
219234
debugPrint('Repository: $owner/$repo');
@@ -249,6 +264,7 @@ class AutoUpdateManager {
249264
BuildContext context,
250265
EnhancedAppUpdateInfo updateInfo,
251266
) {
267+
if (!kEnableSelfUpdate) return; // safety
252268
_showUpdateDialog(context, updateInfo);
253269
}
254270
}

lib/services/update_manager.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ class UpdateManager extends ChangeNotifier {
2727
);
2828
if (Platform.isAndroid) {
2929
if (!kEnableSelfUpdate) {
30-
_statusMessage = 'F-Droidビルドではアプリ内アップデートは無効です。リリースページから手動で更新してください。';
30+
// Hard block for FDroid builds
31+
_statusMessage = 'F-Droidビルドではアプリ内アップデートは無効です (コードパス停止)。';
3132
notifyListeners();
32-
return;
33+
return; // Never attempt install
3334
}
3435
if (fileExtension == 'apk') {
3536
try {
@@ -138,6 +139,10 @@ class UpdateManager extends ChangeNotifier {
138139
}
139140

140141
Future<void> _installApk(String filePath) async {
142+
if (!kEnableSelfUpdate) {
143+
debugPrint('[FDROID] _installApk() suppressed');
144+
return;
145+
}
141146
_statusMessage = 'APKのインストールプロセスを開始しています: $filePath';
142147
notifyListeners();
143148
debugPrint(_statusMessage);

0 commit comments

Comments
 (0)