Skip to content

Commit 11c1952

Browse files
authored
Merge pull request #382 from DanXi-Dev/close-369
add http proxy support
2 parents 435f28c + 9a7ae5e commit 11c1952

20 files changed

+197
-48
lines changed

lib/l10n/intl_en.arb

+6-4
Original file line numberDiff line numberDiff line change
@@ -453,9 +453,6 @@
453453
"theme_type_dark": "Dark",
454454
"theme_type_light": "Light",
455455
"theme_type_system": "System",
456-
"tick_failed": "Failed to check in. Check your internet connection.",
457-
"tick_issue_1": "Failed to check in. Unable to obtain the previous record.\nIf you forgot to check in yesterday, you might need to check in manually.",
458-
"ticking": "Checking in...",
459456
"timetable": "Agenda",
460457
"timetable_refresh_error": "Timetable for the new semester is available. However, we are unable to retrieve it from Fudan Servers.\n\nPlease make sure you are connected to the campus intranet, and try manually refreshing the timetable.\n\nTip: You can pull down at the Agenda page to refresh the timetable manually.",
461458
"tip_that_danxi_is_not_fdu": "Note: Your DanXi Account is not your UIS or Edu-email account.",
@@ -536,5 +533,10 @@
536533
"next_question": "Next",
537534
"prev_question": "Previous",
538535
"sticker": "Stickers",
539-
"welcome_prompt":"Welcome to DanXi!\n\nIf you are using DanXi for the first time, perhaps you would like to take a look at the FAQ section?"
536+
"welcome_prompt": "Welcome to DanXi!\n\nIf you are using DanXi for the first time, perhaps you would like to take a look at the FAQ section?",
537+
"proxy_setting": "Proxy Setting",
538+
"proxy_setting_unset": "Not Set",
539+
"proxy_setting_input_title": "Input HTTP Proxy",
540+
"proxy_setting_input_hint": "e.g. 127.0.0.1:1234 (Leave blank to disable)",
541+
"proxy_setting_set_successfully": "Proxy settings saved, restart the app to take effect."
540542
}

lib/l10n/intl_ja.arb

+8-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"account": "アカウント",
1111
"account_is_set": "アカウント設定完了",
1212
"acknowledgements": "謝辞",
13-
"acknowledgements_markdown": "このアプリのアイコンをデザインしてくれた [IvanFei](https://github.com/ivanfei-1) さん、フォーラム絵文字などのアートデザインを提供してくれた [MSapphire](https://www.xiaohongshu.com/user/profile/6575475f000000001902d794) さん、[Lin2510]() さん、[zzz111]() さんに感謝します。",
13+
"acknowledgements_markdown": "このアプリのアイコンをデザインしてくれた [IvanFei](https://github.com/ivanfei-1) さん、ステッカーなどのアートデザインを提供してくれた [MSapphire](https://www.xiaohongshu.com/user/profile/6575475f000000001902d794) さん、[Lin2510]() さん、[zzz111]() さんに感謝します。",
1414
"add": "追加",
1515
"add_class_time": "授業時間を追加",
1616
"add_courses": "手動でコースを追加する",
@@ -351,7 +351,7 @@
351351
"reason_report_post": "報告理由をご入力してください。(#{id})",
352352
"recommended_tags": "おすすめタグ",
353353
"recommended_tags_availibity": "投稿時にタグをアドバイスする",
354-
"recommended_tags_description": "警告:这是一个实验性功能。推荐的内容可能不正确或者不恰当,仅供参考。如果您发现推荐的内容不恰当,请报告至 dev@fduhole.com 。\n\n所有数据均在本地处理,不会上传至服务器。\n\n模型版本:0.1a (CoreML)\n\n系统要求:iOS 14.0及以上",
354+
"recommended_tags_description": "警告:これは実験的な機能です。推薦された内容は正しくないか不適切である可能性があり、あくまで参考用です。不適切な内容を見つけた場合は、dev@fduhole.com に報告してください。\n\nすべてのデータはローカルで処理され、サーバーにアップロードされることはありません。\n\nモデルバージョン:0.1a (CoreML)\n\nシステム要件:iOS 14.0以降",
355355
"refresh": "更新",
356356
"refresh_timetable_for_new_data": "新しいデータを取得するためにスケジュールを更新してください",
357357
"register_account": "新規アカウント登録",
@@ -445,9 +445,6 @@
445445
"theme_type_dark": "ダーク",
446446
"theme_type_light": "ライト",
447447
"theme_type_system": "システム",
448-
"tick_failed": "打卡失败,请检查网络连接",
449-
"tick_issue_1": "打卡失败,无法获取上次打卡记录。\n出现此错误,很可能是由于您第一次使用旦夕,且昨天忘记打卡所致。\n您需要使用小程序手动完成第一次打卡,从下一次打卡开始,旦夕即可妥善处理此情况。",
450-
"ticking": "チェックイン...",
451448
"timetable": "スケジュール",
452449
"timetable_refresh_error": "新学期のスケジュールが公開されましたが、復旦のサーバーからダウンロードすることができません。\n\n復旦のイントラネットに接続されていることを確認し、手動で授業スケジュールを更新してください。\n\nヒント:「スケジュール」ページで下にスクロールすると、更新できます。",
453450
"tip_that_danxi_is_not_fdu": "注意:旦夕のアカウントは、復旦大学メールやUISアカウントではありません。",
@@ -520,5 +517,10 @@
520517
"enter_forum": "フォーラムへの旅を始めよう",
521518
"next_question": "次へ",
522519
"prev_question": "前へ",
523-
"sticker": "表情"
520+
"sticker": "ステッカー",
521+
"proxy_setting": "プロキシ設定",
522+
"proxy_setting_unset": "未設定",
523+
"proxy_setting_input_title": "HTTPプロキシアドレスを入力",
524+
"proxy_setting_input_hint": "例:127.0.0.1:1234(空白のままにしてプロキシを無効にする)",
525+
"proxy_setting_set_successfully": "プロキシ設定が保存されました。再起動すると有効になります。"
524526
}

lib/l10n/intl_zh_CN.arb

+6-4
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,6 @@
447447
"theme_type_dark": "深色",
448448
"theme_type_light": "浅色",
449449
"theme_type_system": "跟随系统",
450-
"tick_failed": "打卡失败,请检查网络连接",
451-
"tick_issue_1": "打卡失败,无法获取上次打卡记录。\n出现此错误,很可能是由于您第一次使用旦夕,且昨天忘记打卡所致。\n您需要使用小程序手动完成第一次打卡,从下一次打卡开始,旦夕即可妥善处理此情况。",
452-
"ticking": "正在打卡...",
453450
"timetable": "日程",
454451
"timetable_refresh_error": "新学期的课表现已可用,但我们无法从复旦服务器下载新的课表。\n\n请确保您已连接到复旦校园内网,然后尝试手动刷新课表。\n\n提示:您可以在“日程”页面下拉刷新课表。",
455452
"tip_that_danxi_is_not_fdu": "注意:旦夕账号和复旦大学无关,请不要在这里尝试您的复旦邮箱密码(或「统一身份认证」密码)。",
@@ -530,5 +527,10 @@
530527
"next_question": "下一题",
531528
"prev_question": "上一题",
532529
"welcome_prompt":"欢迎来到旦夕!\n\n如果你是第一次使用旦夕,或许你想看看FAQ板块?",
533-
"sticker": "表情"
530+
"sticker": "表情",
531+
"proxy_setting": "代理设置",
532+
"proxy_setting_unset": "未设置",
533+
"proxy_setting_input_title": "输入 HTTP 代理地址",
534+
"proxy_setting_input_hint": "如 127.0.0.1:1234(留空以关闭代理)",
535+
"proxy_setting_set_successfully": "代理设置已保存,重启应用后生效"
534536
}

lib/page/forum/image_viewer.dart

+10-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import 'dart:io';
1919

2020
import 'package:cached_network_image/cached_network_image.dart';
2121
import 'package:dan_xi/generated/l10n.dart';
22+
import 'package:dan_xi/util/io/cache_manager_with_proxy.dart';
23+
import 'package:dan_xi/util/io/dio_utils.dart';
2224
import 'package:dan_xi/util/noticing.dart';
2325
import 'package:dan_xi/util/platform_universal.dart';
2426
import 'package:dan_xi/widget/libraries/error_page_widget.dart';
@@ -54,7 +56,8 @@ import 'package:share_plus/share_plus.dart';
5456
class ImageViewerPage extends StatefulWidget {
5557
final Map<String, dynamic>? arguments;
5658
@protected
57-
final Dio dio = Dio(BaseOptions(responseType: ResponseType.bytes));
59+
final Dio dio =
60+
DioUtils.newDioWithProxy(BaseOptions(responseType: ResponseType.bytes));
5861

5962
static const List<String> IMAGE_SUFFIX = [
6063
'.jpg',
@@ -130,8 +133,8 @@ class ImageViewerPageState extends State<ImageViewerPage> {
130133
}
131134

132135
Future<void> shareImage(BuildContext context) async {
133-
File image =
134-
await DefaultCacheManager().getSingleFile(_imageList[showIndex].hdUrl);
136+
File image = await DefaultCacheManagerWithProxy()
137+
.getSingleFile(_imageList[showIndex].hdUrl);
135138
if (!mounted) return;
136139

137140
if (PlatformX.isMobile) {
@@ -157,8 +160,8 @@ class ImageViewerPageState extends State<ImageViewerPage> {
157160
}
158161

159162
Future<void> saveImage(BuildContext context) async {
160-
File image =
161-
await DefaultCacheManager().getSingleFile(_imageList[showIndex].hdUrl);
163+
File image = await DefaultCacheManagerWithProxy()
164+
.getSingleFile(_imageList[showIndex].hdUrl);
162165
if (PlatformX.isAndroid) {
163166
bool hasPermission = await PlatformX.galleryStorageGranted;
164167
if (!hasPermission && !(await Permission.storage.request().isGranted)) {
@@ -307,7 +310,8 @@ class ImageViewerBodyViewState extends State<ImageViewerBodyView> {
307310
Future<void> cacheOriginalImage() async {
308311
if (widget.imageInfo.thumbUrl == null) return;
309312
try {
310-
await DefaultCacheManager().getSingleFile(widget.imageInfo.hdUrl);
313+
await DefaultCacheManagerWithProxy()
314+
.getSingleFile(widget.imageInfo.hdUrl);
311315
setState(() => originalLoading = false);
312316
} catch (e, st) {
313317
setState(() {

lib/page/subpage_settings.dart

+31-8
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import 'package:dan_xi/repository/forum/forum_repository.dart';
3232
import 'package:dan_xi/util/browser_util.dart';
3333
import 'package:dan_xi/util/flutter_app.dart';
3434
import 'package:dan_xi/util/forum/clean_mode_filter.dart';
35+
import 'package:dan_xi/util/io/cache_manager_with_proxy.dart';
3536
import 'package:dan_xi/util/master_detail_view.dart';
3637
import 'package:dan_xi/util/noticing.dart';
3738
import 'package:dan_xi/util/platform_universal.dart';
@@ -51,7 +52,6 @@ import 'package:flutter/cupertino.dart';
5152
import 'package:flutter/gestures.dart';
5253
import 'package:flutter/material.dart';
5354
import 'package:flutter/services.dart';
54-
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
5555
import 'package:flutter_email_sender/flutter_email_sender.dart';
5656
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
5757
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
@@ -489,6 +489,30 @@ class SettingsSubpageState extends PlatformSubpageState<SettingsSubpage> {
489489
onPressed: () =>
490490
Navigator.of(context).pop()))),
491491
),
492+
ListTile(
493+
title: Text(S.of(context).proxy_setting),
494+
subtitle: Text(
495+
context.select<SettingsProvider, String?>(
496+
(s) => s.proxy) ??
497+
S.of(context).proxy_setting_unset),
498+
leading: const Icon(Icons.network_ping),
499+
onTap: () async {
500+
String? email = await Noticing.showInputDialog(
501+
context,
502+
S.of(context).proxy_setting_input_title,
503+
initialText:
504+
context.read<SettingsProvider>().proxy,
505+
hintText:
506+
S.of(context).proxy_setting_input_hint);
507+
if (!context.mounted || email == null)
508+
return; // return if cancelled
509+
if (email.isEmpty) email = null;
510+
context.read<SettingsProvider>().proxy = email;
511+
await Noticing.showNotice(context,
512+
S.of(context).proxy_setting_set_successfully);
513+
},
514+
enabled: !PlatformX.isWeb,
515+
),
492516
if (context.select<SettingsProvider, bool>(
493517
(value) => value.hiddenNotifications.isNotEmpty))
494518
ListTile(
@@ -665,8 +689,8 @@ class SettingsSubpageState extends PlatformSubpageState<SettingsSubpage> {
665689
builder: (_, bool value, __) => SwitchListTile.adaptive(
666690
title: Text(S.of(context).forum_show_banner),
667691
secondary: const Icon(Icons.campaign),
668-
subtitle: Text(
669-
S.of(context).forum_show_banner_description),
692+
subtitle:
693+
Text(S.of(context).forum_show_banner_description),
670694
value: value,
671695
onChanged: (bool value) =>
672696
SettingsProvider.getInstance().isBannerEnabled =
@@ -677,8 +701,8 @@ class SettingsSubpageState extends PlatformSubpageState<SettingsSubpage> {
677701
builder: (_, bool value, __) => SwitchListTile.adaptive(
678702
title: Text(S.of(context).forum_clean_mode),
679703
secondary: const Icon(Icons.ac_unit),
680-
subtitle: Text(
681-
S.of(context).forum_clean_mode_description),
704+
subtitle:
705+
Text(S.of(context).forum_clean_mode_description),
682706
value: value,
683707
onChanged: (bool value) {
684708
if (value) {
@@ -773,7 +797,7 @@ class SettingsSubpageState extends PlatformSubpageState<SettingsSubpage> {
773797
subtitle: Text(_clearCacheSubtitle ??
774798
S.of(context).clear_cache_description),
775799
onTap: () async {
776-
await DefaultCacheManager().emptyCache();
800+
await DefaultCacheManagerWithProxy().emptyCache();
777801
setState(() {
778802
_clearCacheSubtitle = S.of(context).cache_cleared;
779803
});
@@ -831,8 +855,7 @@ class SettingsSubpageState extends PlatformSubpageState<SettingsSubpage> {
831855
context, S.of(context).login_from_forum_page,
832856
title: S.of(context).login);
833857
} else {
834-
await ForumRepository.getInstance()
835-
.initializeRepo();
858+
await ForumRepository.getInstance().initializeRepo();
836859
onLogout();
837860
refreshSelf();
838861
}

lib/provider/settings_provider.dart

+17
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class SettingsProvider with ChangeNotifier {
8383
static const String KEY_AUTH_BASE_URL = "auth_base_url";
8484
static const String KEY_IMAGE_BASE_URL = "image_base_url";
8585
static const String KEY_DANKE_BASE_URL = "danke_base_url";
86+
static const String KEY_PROXY = "proxy";
8687

8788
SettingsProvider._();
8889

@@ -93,6 +94,22 @@ class SettingsProvider with ChangeNotifier {
9394
/// If you need to get access to a [SettingsProvider], call [context.read<SettingsProvider>()] instead.
9495
factory SettingsProvider.getInstance() => _instance;
9596

97+
String? get proxy {
98+
if (preferences!.containsKey(KEY_PROXY)) {
99+
return preferences!.getString(KEY_PROXY);
100+
}
101+
return null;
102+
}
103+
104+
set proxy(String? value) {
105+
if (value != null) {
106+
preferences!.setString(KEY_PROXY, value);
107+
} else {
108+
preferences!.remove(KEY_PROXY);
109+
}
110+
notifyListeners();
111+
}
112+
96113
List<String> get searchHistory {
97114
if (preferences!.containsKey(KEY_SEARCH_HISTORY)) {
98115
return preferences!.getStringList(KEY_SEARCH_HISTORY) ??

lib/repository/app/announcement_repository.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:dan_xi/common/pubspec.yaml.g.dart';
2222
import 'package:dan_xi/model/announcement.dart';
2323
import 'package:dan_xi/model/celebration.dart';
2424
import 'package:dan_xi/model/extra.dart';
25+
import 'package:dan_xi/util/io/dio_utils.dart';
2526
import 'package:dan_xi/util/public_extension_methods.dart';
2627
import 'package:dan_xi/util/shared_preferences.dart';
2728
import 'package:dio/dio.dart';
@@ -44,7 +45,8 @@ class AnnouncementRepository {
4445
List<Announcement>? _announcementCache;
4546

4647
Future<bool?> loadAnnouncements() async {
47-
final Response<List<dynamic>> response = await Dio().get(_URL);
48+
final Response<List<dynamic>> response =
49+
await DioUtils.newDioWithProxy().get(_URL);
4850
_announcementCache =
4951
response.data?.map((e) => Announcement.fromJson(e)).toList() ?? [];
5052
return _announcementCache?.isNotEmpty ?? false;

lib/repository/base_repository.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import 'package:dan_xi/provider/settings_provider.dart';
1919
import 'package:dan_xi/repository/independent_cookie_jar.dart';
20+
import 'package:dan_xi/util/io/dio_utils.dart';
2021
import 'package:dan_xi/util/io/queued_interceptor.dart';
2122
import 'package:dan_xi/util/io/user_agent_interceptor.dart';
2223
import 'package:dio/dio.dart';
@@ -33,7 +34,7 @@ abstract class BaseRepositoryWithDio {
3334
@protected
3435
Dio get dio {
3536
if (!_dios.containsKey(linkHost)) {
36-
_dios[linkHost] = Dio();
37+
_dios[linkHost] = DioUtils.newDioWithProxy();
3738
_dios[linkHost]!.options = BaseOptions(
3839
receiveDataWhenStatusError: true,
3940
connectTimeout: const Duration(seconds: 10),

lib/repository/fdu/empty_classroom_repository.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'dart:convert';
2020
import 'package:dan_xi/model/person.dart';
2121
import 'package:dan_xi/repository/base_repository.dart';
2222
import 'package:dan_xi/repository/fdu/uis_login_tool.dart';
23+
import 'package:dan_xi/util/io/dio_utils.dart';
2324
import 'package:dio/dio.dart';
2425
import 'package:intl/intl.dart';
2526

@@ -35,7 +36,7 @@ class EmptyClassroomRepository extends BaseRepositoryWithDio {
3536
EmptyClassroomRepository._();
3637

3738
@override
38-
Dio dio = Dio(BaseOptions(
39+
Dio dio = DioUtils.newDioWithProxy(BaseOptions(
3940
connectTimeout: const Duration(seconds: 3),
4041
receiveTimeout: const Duration(seconds: 3)));
4142

lib/repository/fdu/uis_login_tool.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import 'package:beautiful_soup_dart/beautiful_soup.dart';
2121
import 'package:dan_xi/common/constant.dart';
2222
import 'package:dan_xi/model/person.dart';
2323
import 'package:dan_xi/provider/settings_provider.dart';
24-
import 'package:dan_xi/repository/independent_cookie_jar.dart';
2524
import 'package:dan_xi/repository/forum/forum_repository.dart';
25+
import 'package:dan_xi/repository/independent_cookie_jar.dart';
2626
import 'package:dan_xi/util/io/dio_utils.dart';
2727
import 'package:dan_xi/util/io/queued_interceptor.dart';
2828
import 'package:dan_xi/util/io/user_agent_interceptor.dart';
@@ -89,7 +89,7 @@ class UISLoginTool {
8989
Dio dio, String serviceUrl, IndependentCookieJar jar, PersonInfo? info,
9090
[bool forceRelogin = false]) async {
9191
// Create a temporary dio for logging in.
92-
Dio workDio = Dio();
92+
Dio workDio = DioUtils.newDioWithProxy();
9393
workDio.options = BaseOptions(
9494
receiveDataWhenStatusError: true,
9595
connectTimeout: const Duration(seconds: 5),

0 commit comments

Comments
 (0)