Skip to content

Commit 9b264d5

Browse files
authored
Merge pull request #76 from tsukuba-denden/feature/save-code-to-filesystem
2 parents 307094b + 7d28a0b commit 9b264d5

File tree

4 files changed

+86
-12
lines changed

4 files changed

+86
-12
lines changed

lib/models/problem.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
class Problem {
22
final String title;
3+
final String contestId;
34
final String statement;
45
final String constraints;
56
final String inputFormat;
@@ -9,6 +10,7 @@ class Problem {
910

1011
Problem({
1112
required this.title,
13+
required this.contestId,
1214
required this.statement,
1315
required this.constraints,
1416
required this.inputFormat,

lib/screens/editor_screen.dart

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:async'; // TimeoutExceptionのために追加
22
import 'dart:convert';
3+
import 'dart:io';
4+
import 'package:path_provider/path_provider.dart';
35
import 'package:flutter/material.dart';
46
import 'package:flutter/services.dart'; // Clipboardのために追加
57
import 'package:share_plus/share_plus.dart'; // コード共有用
@@ -14,7 +16,6 @@ import 'package:flutter_highlight/themes/monokai-sublime.dart';
1416
import 'package:flutter_highlight/themes/github.dart';
1517
import 'package:google_fonts/google_fonts.dart';
1618
import 'package:http/http.dart' as http; // HTTPリクエスト用
17-
import 'package:shared_preferences/shared_preferences.dart';
1819
import '../models/problem.dart';
1920
import '../models/test_result.dart';
2021
import '../services/atcoder_service.dart';
@@ -78,16 +79,49 @@ class _EditorScreenState extends State<EditorScreen> {
7879
super.dispose();
7980
}
8081

82+
Future<String> _getFilePath() async {
83+
if (_currentProblem == null) {
84+
// 問題がロードされていない場合はデフォルトのパスを返す
85+
return '';
86+
}
87+
final directory = await getApplicationDocumentsDirectory();
88+
final contestId = _currentProblem!.contestId;
89+
// 問題タイトルからファイル名として不適切な文字を削除・置換
90+
final problemTitle = _currentProblem!.title.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_');
91+
final extension = _getExtension(_selectedLanguage);
92+
final path = '${directory.path}/$contestId/$problemTitle/main.$extension';
93+
return path;
94+
}
95+
96+
String _getExtension(String language) {
97+
switch (language) {
98+
case 'Python':
99+
return 'py';
100+
case 'C++':
101+
return 'cpp';
102+
case 'Rust':
103+
return 'rs';
104+
case 'Java':
105+
return 'java';
106+
default:
107+
return 'txt';
108+
}
109+
}
110+
81111
// 保存されたコードを読み込む関数
82112
Future<void> _loadSavedCode() async {
83113
setState(() {
84114
_isLoadingCode = true;
85115
});
86116
try {
87-
final prefs = await SharedPreferences.getInstance();
88-
final savedCode = prefs.getString('code_${widget.problemId}_$_selectedLanguage');
89-
// ★★★ ここで _codeController にアクセスする前に初期化されている必要がある ★★★
90-
if (savedCode != null) {
117+
final filePath = await _getFilePath();
118+
if (filePath.isEmpty) {
119+
_codeController.text = _getTemplateForLanguage(_selectedLanguage);
120+
return;
121+
}
122+
final file = File(filePath);
123+
if (await file.exists()) {
124+
final savedCode = await file.readAsString();
91125
_codeController.text = savedCode;
92126
} else {
93127
_codeController.text = _getTemplateForLanguage(_selectedLanguage);
@@ -106,10 +140,19 @@ class _EditorScreenState extends State<EditorScreen> {
106140
// 現在のコードを保存する関数
107141
Future<void> _saveCode() async {
108142
try {
109-
final prefs = await SharedPreferences.getInstance();
110-
await prefs.setString('code_${widget.problemId}_$_selectedLanguage', _codeController.text);
143+
final filePath = await _getFilePath();
144+
if (filePath.isEmpty) {
145+
ScaffoldMessenger.of(context).showSnackBar(
146+
const SnackBar(content: Text('問題がロードされていないため保存できません')),
147+
);
148+
return;
149+
}
150+
final file = File(filePath);
151+
// ディレクトリが存在しない場合は作成
152+
await file.parent.create(recursive: true);
153+
await file.writeAsString(_codeController.text);
111154
ScaffoldMessenger.of(context).showSnackBar(
112-
SnackBar(content: Text('$_selectedLanguage のコードを保存しました')),
155+
SnackBar(content: Text('コードを $filePath に保存しました')),
113156
);
114157
} catch (e) {
115158
ScaffoldMessenger.of(context).showSnackBar(
@@ -263,9 +306,16 @@ public class Main {
263306
// 現在のコードを復元する関数
264307
Future<void> _restoreCode() async {
265308
try {
266-
final prefs = await SharedPreferences.getInstance();
267-
final savedCode = prefs.getString('code_${widget.problemId}_$_selectedLanguage');
268-
if (savedCode != null) {
309+
final filePath = await _getFilePath();
310+
if (filePath.isEmpty) {
311+
ScaffoldMessenger.of(context).showSnackBar(
312+
const SnackBar(content: Text('問題がロードされていないため復元できません')),
313+
);
314+
return;
315+
}
316+
final file = File(filePath);
317+
if (await file.exists()) {
318+
final savedCode = await file.readAsString();
269319
setState(() {
270320
_codeController.text = savedCode;
271321
});

lib/services/atcoder_service.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ class AtCoderService {
1717
final titleElement = document.querySelector('.h2');
1818
final title = titleElement?.text.trim() ?? 'タイトルが見つかりません';
1919

20+
// コンテストIDの取得
21+
final contestId = _extractContestId(url);
22+
2023
// デバッグ: HTMLの構造を調査
2124
developer.log("HTML構造の分析を開始...");
2225
_analyzeHtmlStructure(document);
@@ -74,6 +77,7 @@ class AtCoderService {
7477

7578
return Problem(
7679
title: title,
80+
contestId: contestId,
7781
statement: statement,
7882
constraints: constraints,
7983
inputFormat: inputFormat,
@@ -89,6 +93,24 @@ class AtCoderService {
8993
rethrow;
9094
}
9195
}
96+
97+
String _extractContestId(String url) {
98+
try {
99+
final uri = Uri.parse(url);
100+
final segments = uri.pathSegments;
101+
if (segments.length >= 2 && segments[0] == 'contests') {
102+
return segments[1];
103+
}
104+
} catch (e) {
105+
// Fallback for invalid URLs
106+
}
107+
// Fallback if contestId cannot be determined
108+
final parts = url.split('/');
109+
if (parts.length > 4 && parts[2] == 'atcoder.jp' && parts[3] == 'contests') {
110+
return parts[4];
111+
}
112+
return 'unknown_contest';
113+
}
92114

93115
// 見出しタイトルからセクションの内容を抽出する
94116
String _extractSectionContent(Element taskStatement, List<String> headingTexts) {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ homepage: https://github.com/tsukuba-denden/shojin_app
44

55
publish_to: 'none'
66

7-
version: 0.6.3-Beta
7+
version: 0.6.4-Beta
88

99
environment:
1010
sdk: ^3.8.1

0 commit comments

Comments
 (0)