Fixed audio cache did't work.
This commit is contained in:
parent
33e34e9966
commit
602cc67342
|
@ -1,5 +1,14 @@
|
|||
# Tsacdop Changelog
|
||||
|
||||
## v0.4.7
|
||||
|
||||
Release date 2020/7/18
|
||||
|
||||
### Bugs fixed
|
||||
|
||||
* Ompl files form other platform import error.
|
||||
* Audio cache did't work.
|
||||
|
||||
## v0.4.6
|
||||
|
||||
Release date 2020/7/17
|
||||
|
|
|
@ -198,6 +198,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"privacyPolicy" : MessageLookupByLibrary.simpleMessage("Privacy Policy"),
|
||||
"published" : m20,
|
||||
"recoverSubscribe" : MessageLookupByLibrary.simpleMessage("Recover subscribe"),
|
||||
"refreshArtwork" : MessageLookupByLibrary.simpleMessage("Update artwork"),
|
||||
"remove" : MessageLookupByLibrary.simpleMessage("Remove"),
|
||||
"removeConfirm" : MessageLookupByLibrary.simpleMessage("Remove confirm"),
|
||||
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("Are you sure you want to unsubscribe?"),
|
||||
|
|
|
@ -198,6 +198,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"privacyPolicy" : MessageLookupByLibrary.simpleMessage("隐私条款"),
|
||||
"published" : m20,
|
||||
"recoverSubscribe" : MessageLookupByLibrary.simpleMessage("恢复订阅"),
|
||||
"refreshArtwork" : MessageLookupByLibrary.simpleMessage("更新头像"),
|
||||
"remove" : MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"removeConfirm" : MessageLookupByLibrary.simpleMessage("取消订阅"),
|
||||
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("您确认要取消订阅吗?"),
|
||||
|
@ -290,8 +291,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"toastRecoverFailed" : MessageLookupByLibrary.simpleMessage("恢复订阅失败"),
|
||||
"toastRemovePlaylist" : MessageLookupByLibrary.simpleMessage("从播放列表移除"),
|
||||
"toastSettingSaved" : MessageLookupByLibrary.simpleMessage("设置已保存"),
|
||||
"toastTimeEqualEnd" : MessageLookupByLibrary.simpleMessage("与结束时间相同"),
|
||||
"toastTimeEqualStart" : MessageLookupByLibrary.simpleMessage("与起始时间相同"),
|
||||
"toastTimeEqualEnd" : MessageLookupByLibrary.simpleMessage("与结束时刻相同"),
|
||||
"toastTimeEqualStart" : MessageLookupByLibrary.simpleMessage("与起始时刻相同"),
|
||||
"understood" : MessageLookupByLibrary.simpleMessage("了解"),
|
||||
"undo" : MessageLookupByLibrary.simpleMessage("撤销"),
|
||||
"unlike" : MessageLookupByLibrary.simpleMessage("取消喜欢"),
|
||||
|
|
|
@ -533,7 +533,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Rate on Play',
|
||||
name: 'feedbackPlay',
|
||||
desc: '',
|
||||
desc: 'Rate on Google Play Store.\nUser can tap to open play link.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -583,7 +583,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Group existed',
|
||||
name: 'groupExisted',
|
||||
desc: '',
|
||||
desc: 'Group name validate in add group dialog. User can\'t add group with same name.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -822,7 +822,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Like date',
|
||||
name: 'likeDate',
|
||||
desc: '',
|
||||
desc: 'Favorite tab, sort by like date.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -862,7 +862,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Mark',
|
||||
name: 'mark',
|
||||
desc: 'The short the best',
|
||||
desc: 'In listen history page, if a episode is marked as listened.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -1271,6 +1271,16 @@ class S {
|
|||
return Intl.message(
|
||||
'Recover subscribe',
|
||||
name: 'recoverSubscribe',
|
||||
desc: 'User can recover subscribe podcast after remove it in subscribe history page.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Update artwork`
|
||||
String get refreshArtwork {
|
||||
return Intl.message(
|
||||
'Update artwork',
|
||||
name: 'refreshArtwork',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
|
@ -1281,7 +1291,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Remove',
|
||||
name: 'remove',
|
||||
desc: '',
|
||||
desc: 'Remove not "removed". \nRemove a podcast or a group.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -1554,7 +1564,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Discovery Features Again',
|
||||
name: 'settingsDiscovery',
|
||||
desc: '',
|
||||
desc: 'Reset feature discovery state. User tap it and restart app, will see features tutorial again.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -2054,7 +2064,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Last time $time',
|
||||
name: 'timeLastPlayed',
|
||||
desc: '',
|
||||
desc: 'Show last time stop position in player when a episode have been played.',
|
||||
args: [time],
|
||||
);
|
||||
}
|
||||
|
@ -2094,7 +2104,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Discovery feature reopened, pleast restart the app',
|
||||
name: 'toastDiscovery',
|
||||
desc: '',
|
||||
desc: 'Toast displayed when user tap Discovery Features Again in settings page.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -2204,7 +2214,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Time is equal to end time',
|
||||
name: 'toastTimeEqualEnd',
|
||||
desc: '',
|
||||
desc: 'User can\'t choose the same time as schedule end time.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
@ -2214,7 +2224,7 @@ class S {
|
|||
return Intl.message(
|
||||
'Time is equal to start time',
|
||||
name: 'toastTimeEqualStart',
|
||||
desc: '',
|
||||
desc: 'User can\'t choose the same time as schedule start time.',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:line_icons/line_icons.dart';
|
|||
|
||||
import '../util/context_extension.dart';
|
||||
|
||||
const String version = '0.4.6';
|
||||
const String version = '0.4.7';
|
||||
|
||||
class AboutApp extends StatelessWidget {
|
||||
_launchUrl(String url) async {
|
||||
|
|
|
@ -107,7 +107,9 @@
|
|||
"feedbackGithub": "Submit issue",
|
||||
"@feedbackGithub": {},
|
||||
"feedbackPlay": "Rate on Play",
|
||||
"@feedbackPlay": {},
|
||||
"@feedbackPlay": {
|
||||
"description": "Rate on Google Play Store.\nUser can tap to open play link."
|
||||
},
|
||||
"feedbackTelegram": "Join group",
|
||||
"@feedbackTelegram": {},
|
||||
"fonts": "Fonts",
|
||||
|
@ -121,7 +123,9 @@
|
|||
"goodNight": "Good Night",
|
||||
"@goodNight": {},
|
||||
"groupExisted": "Group existed",
|
||||
"@groupExisted": {},
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
},
|
||||
"groupFilter": "Group filter",
|
||||
"@groupFilter": {},
|
||||
"groupRemoveConfirm": "Are you sure you want to delete this group? Podcasts will be moved to Home group.",
|
||||
|
@ -171,7 +175,9 @@
|
|||
"liked": "Liked",
|
||||
"@liked": {},
|
||||
"likeDate": "Like date",
|
||||
"@likeDate": {},
|
||||
"@likeDate": {
|
||||
"description": "Favorite tab, sort by like date."
|
||||
},
|
||||
"listen": "Listen",
|
||||
"@listen": {},
|
||||
"listened": "Listened",
|
||||
|
@ -180,7 +186,7 @@
|
|||
"@loadMore": {},
|
||||
"mark": "Mark",
|
||||
"@mark": {
|
||||
"description": "The short the best"
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
},
|
||||
"markConfirm": "Mark confirm",
|
||||
"@markConfirm": {},
|
||||
|
@ -291,9 +297,15 @@
|
|||
}
|
||||
},
|
||||
"recoverSubscribe": "Recover subscribe",
|
||||
"@recoverSubscribe": {},
|
||||
"@recoverSubscribe": {
|
||||
"description": "User can recover subscribe podcast after remove it in subscribe history page."
|
||||
},
|
||||
"refreshArtwork": "Update artwork",
|
||||
"@refreshArtwork": {},
|
||||
"remove": "Remove",
|
||||
"@remove": {},
|
||||
"@remove": {
|
||||
"description": "Remove not \"removed\". \nRemove a podcast or a group."
|
||||
},
|
||||
"removeConfirm": "Remove confirm",
|
||||
"@removeConfirm": {
|
||||
"description": "unsubscribe podcast dialog"
|
||||
|
@ -354,7 +366,9 @@
|
|||
"settingsDefaultGridRecent": "Recent tab",
|
||||
"@settingsDefaultGridRecent": {},
|
||||
"settingsDiscovery": "Discovery Features Again",
|
||||
"@settingsDiscovery": {},
|
||||
"@settingsDiscovery": {
|
||||
"description": "Reset feature discovery state. User tap it and restart app, will see features tutorial again."
|
||||
},
|
||||
"settingsEnableSyncing": "Enable syncing",
|
||||
"@settingsEnableSyncing": {},
|
||||
"settingsEnableSyncingDes": "Refresh all podcasts in the background to get leatest episodes",
|
||||
|
@ -455,6 +469,7 @@
|
|||
"@systemDefault": {},
|
||||
"timeLastPlayed": "Last time {time}",
|
||||
"@timeLastPlayed": {
|
||||
"description": "Show last time stop position in player when a episode have been played.",
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
|
@ -474,7 +489,9 @@
|
|||
"toastAddPlaylist": "Added to playlist",
|
||||
"@toastAddPlaylist": {},
|
||||
"toastDiscovery": "Discovery feature reopened, pleast restart the app",
|
||||
"@toastDiscovery": {},
|
||||
"@toastDiscovery": {
|
||||
"description": "Toast displayed when user tap Discovery Features Again in settings page."
|
||||
},
|
||||
"toastFileError": "File error, subscribe failed",
|
||||
"@toastFileError": {},
|
||||
"toastFileNotValid": "File not valid",
|
||||
|
@ -500,9 +517,13 @@
|
|||
"toastSettingSaved": "Setting saved",
|
||||
"@toastSettingSaved": {},
|
||||
"toastTimeEqualEnd": "Time is equal to end time",
|
||||
"@toastTimeEqualEnd": {},
|
||||
"@toastTimeEqualEnd": {
|
||||
"description": "User can't choose the same time as schedule end time."
|
||||
},
|
||||
"toastTimeEqualStart": "Time is equal to start time",
|
||||
"@toastTimeEqualStart": {},
|
||||
"@toastTimeEqualStart": {
|
||||
"description": "User can't choose the same time as schedule start time."
|
||||
},
|
||||
"understood": "Understood",
|
||||
"@understood": {},
|
||||
"undo": "UNDO",
|
||||
|
|
|
@ -107,7 +107,9 @@
|
|||
"feedbackGithub": "提交Issue",
|
||||
"@feedbackGithub": {},
|
||||
"feedbackPlay": "Play评价",
|
||||
"@feedbackPlay": {},
|
||||
"@feedbackPlay": {
|
||||
"description": "Rate on Google Play Store.\nUser can tap to open play link."
|
||||
},
|
||||
"feedbackTelegram": "加入小组",
|
||||
"@feedbackTelegram": {},
|
||||
"fonts": "字体",
|
||||
|
@ -121,7 +123,9 @@
|
|||
"goodNight": "晚安",
|
||||
"@goodNight": {},
|
||||
"groupExisted": "组名已使用",
|
||||
"@groupExisted": {},
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
},
|
||||
"groupFilter": "分组",
|
||||
"@groupFilter": {},
|
||||
"groupRemoveConfirm": "您确认要移除该分组吗?播客将被移动到 Home 分组。",
|
||||
|
@ -171,7 +175,9 @@
|
|||
"liked": "已收藏",
|
||||
"@liked": {},
|
||||
"likeDate": "收藏日期",
|
||||
"@likeDate": {},
|
||||
"@likeDate": {
|
||||
"description": "Favorite tab, sort by like date."
|
||||
},
|
||||
"listen": "收听",
|
||||
"@listen": {},
|
||||
"listened": "已收听",
|
||||
|
@ -180,7 +186,7 @@
|
|||
"@loadMore": {},
|
||||
"mark": "标记",
|
||||
"@mark": {
|
||||
"description": "The short the best"
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
},
|
||||
"markConfirm": "确认标记",
|
||||
"@markConfirm": {},
|
||||
|
@ -291,9 +297,15 @@
|
|||
}
|
||||
},
|
||||
"recoverSubscribe": "恢复订阅",
|
||||
"@recoverSubscribe": {},
|
||||
"@recoverSubscribe": {
|
||||
"description": "User can recover subscribe podcast after remove it in subscribe history page."
|
||||
},
|
||||
"refreshArtwork": "更新头像",
|
||||
"@refreshArtwork": {},
|
||||
"remove": "移除",
|
||||
"@remove": {},
|
||||
"@remove": {
|
||||
"description": "Remove not \"removed\". \nRemove a podcast or a group."
|
||||
},
|
||||
"removeConfirm": "取消订阅",
|
||||
"@removeConfirm": {
|
||||
"description": "unsubscribe podcast dialog"
|
||||
|
@ -354,7 +366,9 @@
|
|||
"settingsDefaultGridRecent": "最近页",
|
||||
"@settingsDefaultGridRecent": {},
|
||||
"settingsDiscovery": "再次功能介绍",
|
||||
"@settingsDiscovery": {},
|
||||
"@settingsDiscovery": {
|
||||
"description": "Reset feature discovery state. User tap it and restart app, will see features tutorial again."
|
||||
},
|
||||
"settingsEnableSyncing": "开启自动更新",
|
||||
"@settingsEnableSyncing": {},
|
||||
"settingsEnableSyncingDes": "在后台更新所有订阅播客",
|
||||
|
@ -455,6 +469,7 @@
|
|||
"@systemDefault": {},
|
||||
"timeLastPlayed": "上次播放{time}",
|
||||
"@timeLastPlayed": {
|
||||
"description": "Show last time stop position in player when a episode have been played.",
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
|
@ -474,7 +489,9 @@
|
|||
"toastAddPlaylist": "添加到播放列表",
|
||||
"@toastAddPlaylist": {},
|
||||
"toastDiscovery": "重启应用后可查看",
|
||||
"@toastDiscovery": {},
|
||||
"@toastDiscovery": {
|
||||
"description": "Toast displayed when user tap Discovery Features Again in settings page."
|
||||
},
|
||||
"toastFileError": "文件错误,导入失败",
|
||||
"@toastFileError": {},
|
||||
"toastFileNotValid": "文件错误",
|
||||
|
@ -499,10 +516,14 @@
|
|||
"@toastRemovePlaylist": {},
|
||||
"toastSettingSaved": "设置已保存",
|
||||
"@toastSettingSaved": {},
|
||||
"toastTimeEqualEnd": "与结束时间相同",
|
||||
"@toastTimeEqualEnd": {},
|
||||
"toastTimeEqualStart": "与起始时间相同",
|
||||
"@toastTimeEqualStart": {},
|
||||
"toastTimeEqualEnd": "与结束时刻相同",
|
||||
"@toastTimeEqualEnd": {
|
||||
"description": "User can't choose the same time as schedule end time."
|
||||
},
|
||||
"toastTimeEqualStart": "与起始时刻相同",
|
||||
"@toastTimeEqualStart": {
|
||||
"description": "User can't choose the same time as schedule start time."
|
||||
},
|
||||
"understood": "了解",
|
||||
"@understood": {},
|
||||
"undo": "撤销",
|
||||
|
|
|
@ -150,13 +150,16 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(widget.podcastLocal.title,
|
||||
child: Text(
|
||||
widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline5
|
||||
.copyWith(color: Colors.white))),
|
||||
.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,13 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
AnimationController _controller;
|
||||
Animation<double> _animation;
|
||||
_getCacheMax() async {
|
||||
int cache = await cacheStorage.getInt();
|
||||
int value = cache == 0 ? 200 : cache ~/ (1024 * 1024);
|
||||
int cache =
|
||||
await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt());
|
||||
if (cache == 0) {
|
||||
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
||||
cache = 200 * 1024 * 1024;
|
||||
}
|
||||
int value = cache ~/ (1024 * 1024);
|
||||
if (value > 100) {
|
||||
_controller = AnimationController(
|
||||
vsync: this, duration: Duration(milliseconds: value * 2));
|
||||
|
|
|
@ -671,7 +671,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
bool _lostFocus;
|
||||
bool _playing;
|
||||
bool _stopAtEnd;
|
||||
int cacheMax;
|
||||
int _cacheMax;
|
||||
bool get hasNext => _queue.length > 0;
|
||||
|
||||
MediaItem get mediaItem => _queue.length > 0 ? _queue.first : null;
|
||||
|
@ -699,6 +699,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
Future<void> onStart() async {
|
||||
_stopAtEnd = false;
|
||||
_lostFocus = false;
|
||||
|
||||
var playerStateSubscription = _audioPlayer.playbackStateStream
|
||||
.where((state) => state == AudioPlaybackState.completed)
|
||||
.listen((state) {
|
||||
|
@ -759,7 +760,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
} else {
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax);
|
||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
||||
print(mediaItem.title);
|
||||
Duration duration = await _audioPlayer.durationFuture;
|
||||
if (duration != null)
|
||||
|
@ -781,11 +782,14 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
if (_skipState == null) {
|
||||
if (_playing == null) {
|
||||
_playing = true;
|
||||
int cacheMax =
|
||||
await cacheStorage.getInt(defaultValue: 200 * 1024 * 1024);
|
||||
|
||||
_cacheMax = await cacheStorage.getInt(
|
||||
defaultValue: (200 * 1024 * 1024).toInt());
|
||||
// await AudioServiceBackground.setQueue(_queue);
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax);
|
||||
if (_cacheMax == 0) {
|
||||
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
||||
_cacheMax = 200 * 1024 * 1024;
|
||||
}
|
||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
||||
var duration = await _audioPlayer.durationFuture;
|
||||
if (duration != null)
|
||||
await AudioServiceBackground.setMediaItem(
|
||||
|
@ -886,7 +890,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
_queue.insert(0, mediaItem);
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax);
|
||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
||||
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||
AudioServiceBackground.setMediaItem(
|
||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
||||
|
|
|
@ -464,18 +464,19 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
await Dio().get<List<int>>(p.itunes.image.href,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 60000,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
imageUrl = p.itunes.image.href;
|
||||
img.Image image = img.decodeImage(imageResponse.data);
|
||||
thumbnail = img.copyResize(image, width: 300);
|
||||
} catch (e) {
|
||||
try {
|
||||
Response<List<int>> imageResponse = await Dio(BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 60000,
|
||||
)).get<List<int>>(item.imgUrl,
|
||||
options: Options(responseType: ResponseType.bytes));
|
||||
Response<List<int>> imageResponse =
|
||||
await Dio().get<List<int>>(item.imgUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
imageUrl = item.imgUrl;
|
||||
img.Image image = img.decodeImage(imageResponse.data);
|
||||
thumbnail = img.copyResize(image, width: 300);
|
||||
|
|
|
@ -7,12 +7,13 @@ import '../local_storage/key_value_storage.dart';
|
|||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
|
||||
enum RefreshState { none, fetch, error }
|
||||
enum RefreshState { none, fetch, error, artwork }
|
||||
|
||||
class RefreshItem {
|
||||
String title;
|
||||
RefreshState refreshState;
|
||||
RefreshItem(this.title, this.refreshState);
|
||||
bool artwork;
|
||||
RefreshItem(this.title, this.refreshState, {this.artwork = false});
|
||||
}
|
||||
|
||||
class RefreshWorker extends ChangeNotifier {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: tsacdop
|
||||
description: An easy-use podacasts player.
|
||||
|
||||
version: 0.4.6+22
|
||||
version: 0.4.7+23
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
|
|
Loading…
Reference in New Issue