@@ -17,7 +17,9 @@ class AddTelegramSettingState extends State<AddTelegramSetting> {
1717 final TextEditingController _tokenController = TextEditingController ();
1818 final TextEditingController _remarkController = TextEditingController ();
1919 final TextEditingController _tagController = TextEditingController ();
20+ final _formKey = GlobalKey <FormState >();
2021 int _syncType = 3 ;
22+ bool _isLoading = false ;
2123 final TelegramSyncSettingsController _settingsController = locator <TelegramSyncSettingsController >();
2224
2325 @override
@@ -29,87 +31,230 @@ class AddTelegramSettingState extends State<AddTelegramSetting> {
2931 }
3032 }
3133
34+ @override
35+ void dispose () {
36+ _channelIdController.dispose ();
37+ _channelNameController.dispose ();
38+ _tokenController.dispose ();
39+ _remarkController.dispose ();
40+ _tagController.dispose ();
41+ super .dispose ();
42+ }
43+
44+ Widget _buildInputField ({
45+ required TextEditingController controller,
46+ required String label,
47+ String ? hint,
48+ String ? Function (String ? )? validator,
49+ bool obscureText = false ,
50+ TextInputType ? keyboardType,
51+ int maxLines = 1 ,
52+ }) {
53+ return Padding (
54+ padding: const EdgeInsets .only (bottom: 16 ),
55+ child: TextFormField (
56+ controller: controller,
57+ obscureText: obscureText,
58+ maxLines: maxLines,
59+ keyboardType: keyboardType,
60+ decoration: InputDecoration (
61+ labelText: label,
62+ hintText: hint,
63+ border: const OutlineInputBorder (),
64+ contentPadding: const EdgeInsets .symmetric (horizontal: 16 , vertical: 12 ),
65+ errorMaxLines: 2 ,
66+ ),
67+ validator: validator ?? (value) {
68+ if (value == null || value.trim ().isEmpty) {
69+ return '$label is required' ;
70+ }
71+ return null ;
72+ },
73+ ),
74+ );
75+ }
76+
77+ Future <void > _saveSetting () async {
78+ if (! _formKey.currentState! .validate ()) return ;
79+
80+ setState (() => _isLoading = true );
81+
82+ try {
83+ await _settingsController.addTelegramSetting (
84+ TelegramSettings (
85+ syncType: _syncType,
86+ syncValue: _syncType == 4 ? _tagController.text : '' ,
87+ channelId: _channelIdController.text.trim (),
88+ channelName: _channelNameController.text.trim (),
89+ tokenRemark: _remarkController.text.trim (),
90+ encryptedToken: _tokenController.text.trim (),
91+ ),
92+ );
93+ if (mounted) {
94+ Navigator .of (context).pop ();
95+ }
96+ } catch (e) {
97+ if (mounted) {
98+ ScaffoldMessenger .of (context).showSnackBar (
99+ SnackBar (
100+ content: Text ('Failed to save settings: ${e .toString ()}' ),
101+ backgroundColor: Colors .red,
102+ ),
103+ );
104+ }
105+ } finally {
106+ if (mounted) {
107+ setState (() => _isLoading = false );
108+ }
109+ }
110+ }
111+
32112 @override
33113 Widget build (BuildContext context) {
34114 return Scaffold (
35115 appBar: AppBar (
36116 title: const Text ('Add Sync Setting - Telegram' ),
37- ),
38- body: Padding (
39- padding: const EdgeInsets .all (16.0 ),
40- child: Column (
41- crossAxisAlignment: CrossAxisAlignment .start,
42- children: [
43- const Padding (
44- padding: EdgeInsets .symmetric (vertical: 8.0 ),
45- child: Text (
46- 'Source Note' ,
47- style: TextStyle (fontSize: 16 , color: Colors .black54),
117+ actions: [
118+ if (_isLoading)
119+ const Center (
120+ child: Padding (
121+ padding: EdgeInsets .symmetric (horizontal: 16.0 ),
122+ child: SizedBox (
123+ width: 20 ,
124+ height: 20 ,
125+ child: CircularProgressIndicator (
126+ strokeWidth: 2 ,
127+ valueColor: AlwaysStoppedAnimation <Color >(Colors .white),
128+ ),
129+ ),
48130 ),
49131 ),
50- DropdownButton <int >(
51- value: _syncType,
52- onChanged: (int ? newValue) {
53- setState (() {
54- _syncType = newValue! ;
55- });
56- },
57- items: const [
58- DropdownMenuItem <int >(
59- value: 1 ,
60- child: Text ('Public' ),
61- ),
62- DropdownMenuItem <int >(
63- value: 2 ,
64- child: Text ('Private' ),
65- ),
66- DropdownMenuItem <int >(
67- value: 3 ,
68- child: Text ('All' ),
132+ ],
133+ ),
134+ body: Form (
135+ key: _formKey,
136+ child: SingleChildScrollView (
137+ padding: const EdgeInsets .all (16 ),
138+ child: Column (
139+ crossAxisAlignment: CrossAxisAlignment .start,
140+ children: [
141+ Card (
142+ child: Padding (
143+ padding: const EdgeInsets .all (16 ),
144+ child: Column (
145+ crossAxisAlignment: CrossAxisAlignment .start,
146+ children: [
147+ const Text (
148+ 'Sync Settings' ,
149+ style: TextStyle (
150+ fontSize: 18 ,
151+ fontWeight: FontWeight .bold,
152+ ),
153+ ),
154+ const SizedBox (height: 16 ),
155+ DropdownButtonFormField <int >(
156+ decoration: const InputDecoration (
157+ labelText: 'Source Note' ,
158+ border: OutlineInputBorder (),
159+ contentPadding: EdgeInsets .symmetric (horizontal: 16 , vertical: 12 ),
160+ ),
161+ value: _syncType,
162+ items: const [
163+ DropdownMenuItem <int >(
164+ value: 1 ,
165+ child: Text ('Public Notes' ),
166+ ),
167+ DropdownMenuItem <int >(
168+ value: 2 ,
169+ child: Text ('Private Notes' ),
170+ ),
171+ DropdownMenuItem <int >(
172+ value: 3 ,
173+ child: Text ('All Notes (Public + Private)' ),
174+ ),
175+ DropdownMenuItem <int >(
176+ value: 4 ,
177+ child: Text ('Notes with Specific Tag' ),
178+ ),
179+ ],
180+ onChanged: (value) => setState (() => _syncType = value! ),
181+ validator: (value) {
182+ if (value == null ) {
183+ return 'Please select a source note type' ;
184+ }
185+ return null ;
186+ },
187+ ),
188+ const SizedBox (height: 16 ),
189+ if (_syncType == 4 )
190+ _buildInputField (
191+ controller: _tagController,
192+ label: 'Tag' ,
193+ hint: 'Enter tag text, such as "memories"' ,
194+ validator: (value) {
195+ if (_syncType == 4 && (value == null || value.trim ().isEmpty)) {
196+ return 'Tag is required for tag-based sync' ;
197+ }
198+ return null ;
199+ },
200+ ),
201+ _buildInputField (
202+ controller: _channelIdController,
203+ label: 'Channel ID' ,
204+ hint: 'Telegram channel ID' ,
205+ keyboardType: TextInputType .text,
206+ ),
207+ _buildInputField (
208+ controller: _channelNameController,
209+ label: 'Channel Name' ,
210+ hint: 'Channel name that helps you remember' ,
211+ keyboardType: TextInputType .text,
212+ ),
213+ _buildInputField (
214+ controller: _tokenController,
215+ label: 'Telegram Bot Token' ,
216+ hint: 'Your bot token' ,
217+ obscureText: false ,
218+ ),
219+ _buildInputField (
220+ controller: _remarkController,
221+ label: 'Token Remark' ,
222+ hint: 'Add a description for this token' ,
223+ maxLines: 2 ,
224+ ),
225+ ],
226+ ),
69227 ),
70- DropdownMenuItem <int >(
71- value: 4 ,
72- child: Text ('Tag' ),
228+ ),
229+ const SizedBox (height: 24 ),
230+ SizedBox (
231+ width: double .infinity,
232+ child: ElevatedButton (
233+ style: ElevatedButton .styleFrom (
234+ padding: const EdgeInsets .symmetric (vertical: 16 ),
235+ backgroundColor: Theme .of (context).primaryColor,
236+ foregroundColor: Colors .white,
237+ ),
238+ onPressed: _isLoading ? null : _saveSetting,
239+ child: _isLoading
240+ ? const SizedBox (
241+ height: 20 ,
242+ width: 20 ,
243+ child: CircularProgressIndicator (
244+ strokeWidth: 2 ,
245+ valueColor: AlwaysStoppedAnimation <Color >(Colors .white),
246+ ),
247+ )
248+ : const Text (
249+ 'Save Settings' ,
250+ style: TextStyle (fontSize: 16 ),
251+ ),
73252 ),
74- ],
75- ),
76- if (_syncType == 4 )
77- TextField (
78- controller: _tagController,
79- decoration: const InputDecoration (labelText: 'Tag' ),
80253 ),
81- TextField (
82- controller: _channelIdController,
83- decoration: const InputDecoration (labelText: 'Channel ID' ),
84- ),
85- TextField (
86- controller: _channelNameController,
87- decoration: const InputDecoration (labelText: 'Channel Name' ),
88- ),
89- TextField (
90- controller: _tokenController,
91- decoration: const InputDecoration (labelText: 'Telegram Bot Token' ),
92- ),
93- TextField (
94- controller: _remarkController,
95- decoration: const InputDecoration (labelText: 'Token Remark' ),
96- ),
97- const SizedBox (height: 20 ),
98- ElevatedButton (
99- onPressed: () {
100- _settingsController.addTelegramSetting (TelegramSettings (
101- syncType: _syncType,
102- syncValue: _syncType == 4 ? _tagController.text : '' ,
103- channelId: _channelIdController.text,
104- channelName: _channelNameController.text,
105- tokenRemark: _remarkController.text,
106- encryptedToken: _tokenController.text)).then (Navigator .of (context).pop);
107- },
108- child: const Text ('Save' ),
109- ),
110- ],
254+ ],
255+ ),
111256 ),
112257 ),
113258 );
114259 }
115- }
260+ }
0 commit comments