diff --git a/plugins/native-ui/config.py b/plugins/native-ui/config.py
index b0273fa..04e992a 100644
--- a/plugins/native-ui/config.py
+++ b/plugins/native-ui/config.py
@@ -1,5 +1,137 @@
 # -*- coding: utf-8 -*-
+import configparser
+import logging
 import os
+from typing import *
+
+import pubsub.pub as pub
+
+logger = logging.getLogger('native-ui.' + __name__)
 
 BASE_PATH = os.path.realpath(os.getcwd())
 LOG_PATH = os.path.join(BASE_PATH, 'log')
+DATA_PATH = os.path.join(BASE_PATH, 'data')
+
+SAVE_CONFIG_PATH = os.path.join(DATA_PATH, 'config.ini')
+CONFIG_PATH_LIST = [
+    SAVE_CONFIG_PATH,
+    os.path.join(DATA_PATH, 'config.example.ini')
+]
+
+_config: Optional['AppConfig'] = None
+
+
+def init():
+    if reload():
+        return
+    logger.warning('Using default config')
+    set_config(AppConfig())
+
+
+def reload():
+    config_path = ''
+    for path in CONFIG_PATH_LIST:
+        if os.path.exists(path):
+            config_path = path
+            break
+    if config_path == '':
+        return False
+
+    config = AppConfig()
+    if not config.load(config_path):
+        return False
+
+    set_config(config)
+    return True
+
+
+def get_config():
+    return _config
+
+
+def set_config(new_config: 'AppConfig'):
+    global _config
+    old_config = _config
+    _config = new_config
+
+    if old_config is not None and new_config is not old_config:
+        pub.sendMessage('config_change', new_config=new_config, old_config=old_config)
+
+
+class AppConfig:
+    def __init__(self):
+        self.room_opacity = 100
+
+        self.chat_url_params = self._get_default_url_params()
+        self.paid_url_params = self._get_default_url_params()
+
+    @staticmethod
+    def _get_default_url_params():
+        return {
+            'minGiftPrice': '0',
+            'showGiftName': 'true',
+            'maxNumber': '200',
+        }
+
+    def is_url_params_changed(self, other: 'AppConfig'):
+        return self.chat_url_params != other.chat_url_params or self.paid_url_params != other.paid_url_params
+
+    def load(self, path):
+        try:
+            config = configparser.ConfigParser()
+            config.read(path, 'utf-8-sig')
+
+            self._load_ui_config(config)
+            self._load_url_params(config)
+        except Exception:  # noqa
+            logger.exception('Failed to load config:')
+            return False
+        return True
+
+    def save(self, path):
+        try:
+            config = configparser.ConfigParser()
+
+            self._save_ui_config(config)
+            self._save_url_params(config)
+
+            tmp_path = path + '.tmp'
+            with open(tmp_path, 'w', encoding='utf-8-sig') as f:
+                config.write(f)
+            os.replace(tmp_path, path)
+        except Exception:  # noqa
+            logger.exception('Failed to save config:')
+            return False
+        return True
+
+    def _load_ui_config(self, config: configparser.ConfigParser):
+        ui_section = config['ui']
+        self.room_opacity = ui_section.getint('room_opacity', self.room_opacity)
+
+    def _save_ui_config(self, config: configparser.ConfigParser):
+        config['ui'] = {
+            'room_opacity': str(self.room_opacity),
+        }
+
+    def _load_url_params(self, config: configparser.ConfigParser):
+        self.chat_url_params = self._section_to_url_params(config['chat_url_params'])
+        self.paid_url_params = self._section_to_url_params(config['paid_url_params'])
+
+    @staticmethod
+    def _section_to_url_params(section: configparser.SectionProxy):
+        params = {}
+        for line in section.values():
+            key, _, value = line.partition('=')
+            params[key.strip()] = value.strip()
+        return params
+
+    def _save_url_params(self, config: configparser.ConfigParser):
+        config['chat_url_params'] = self._url_params_to_section(self.chat_url_params)
+        config['paid_url_params'] = self._url_params_to_section(self.paid_url_params)
+
+    @staticmethod
+    def _url_params_to_section(url_params: dict):
+        return {
+            str(index): f'{key} = {value}'
+            for index, (key, value) in enumerate(url_params.items(), 1)
+        }
diff --git a/plugins/native-ui/data/config.example.ini b/plugins/native-ui/data/config.example.ini
new file mode 100644
index 0000000..1af6b4f
--- /dev/null
+++ b/plugins/native-ui/data/config.example.ini
@@ -0,0 +1,47 @@
+# 如果要修改配置,可以复制此文件并重命名为“config.ini”再修改
+
+[ui]
+# 房间窗口不透明度
+room_opacity = 100
+
+
+# 评论栏浏览器URL中的参数
+[chat_url_params]
+# --- 这些参数不可改变 ---
+# 通过服务器转发消息
+# 1 = relayMessagesByServer = true
+
+# --- 这些参数可以在UI中设置 ---
+# 自动翻译弹幕到日语
+1 = autoTranslate = false
+# 标注打赏用户名读音
+2 = giftUsernamePronunciation =
+# 最低显示打赏价格(元)
+3 = minGiftPrice = 0
+# 屏蔽礼物弹幕
+4 = blockGiftDanmaku = true
+
+# --- 其他的参数自己发挥 ---
+# 显示礼物名
+5 = showGiftName = true
+# 最大弹幕数
+6 = maxNumber = 200
+
+
+# 付费消息浏览器URL中的参数
+[paid_url_params]
+# --- 这些参数不可改变 ---
+# 通过服务器转发消息
+# 1 = relayMessagesByServer = true
+# 显示弹幕
+# 2 = showDanmaku = false
+
+# --- 这些参数可以在UI中设置 ---
+1 = autoTranslate = false
+2 = giftUsernamePronunciation =
+3 = minGiftPrice = 0
+4 = blockGiftDanmaku = true
+
+# --- 其他的参数自己发挥 ---
+5 = showGiftName = true
+6 = maxNumber = 200
diff --git a/plugins/native-ui/designer/native-ui.fbp b/plugins/native-ui/designer/native-ui.fbp
index 29f7f77..556e39c 100644
--- a/plugins/native-ui/designer/native-ui.fbp
+++ b/plugins/native-ui/designer/native-ui.fbp
@@ -443,7 +443,7 @@
             <object class="notebookpage" expanded="true">
               <property name="bitmap"></property>
               <property name="label">付费消息</property>
-              <property name="select">0</property>
+              <property name="select">1</property>
               <object class="wxPanel" expanded="true">
                 <property name="BottomDockable">1</property>
                 <property name="LeftDockable">1</property>
@@ -824,7 +824,7 @@
             <object class="notebookpage" expanded="true">
               <property name="bitmap"></property>
               <property name="label">统计</property>
-              <property name="select">1</property>
+              <property name="select">0</property>
               <object class="wxPanel" expanded="true">
                 <property name="BottomDockable">1</property>
                 <property name="LeftDockable">1</property>
@@ -1134,7 +1134,7 @@
       <property name="name">RoomConfigDialogBase</property>
       <property name="pos"></property>
       <property name="size">-1,-1</property>
-      <property name="style">wxDEFAULT_DIALOG_STYLE</property>
+      <property name="style">wxDEFAULT_DIALOG_STYLE|wxDIALOG_NO_PARENT</property>
       <property name="subclass">; ; forward_declare</property>
       <property name="title">房间设置</property>
       <property name="tooltip"></property>
@@ -1288,7 +1288,7 @@
                     <property name="pos"></property>
                     <property name="resize">Resizable</property>
                     <property name="show">1</property>
-                    <property name="size">-1,-1</property>
+                    <property name="size">200,-1</property>
                     <property name="style">wxSL_HORIZONTAL</property>
                     <property name="subclass">; ; forward_declare</property>
                     <property name="toolbar_pane">0</property>
@@ -1301,6 +1301,7 @@
                     <property name="window_extra_style"></property>
                     <property name="window_name"></property>
                     <property name="window_style"></property>
+                    <event name="OnSlider">_on_opacity_slider_change</event>
                   </object>
                 </object>
                 <object class="sizeritem" expanded="true">
@@ -1472,7 +1473,7 @@
                     <property name="minimize_button">0</property>
                     <property name="minimum_size"></property>
                     <property name="moveable">1</property>
-                    <property name="name">auto_translate_label1</property>
+                    <property name="name">gift_pron_label</property>
                     <property name="pane_border">1</property>
                     <property name="pane_position"></property>
                     <property name="pane_size"></property>
@@ -1503,9 +1504,9 @@
                     <property name="permission">none</property>
                     <object class="sizeritem" expanded="true">
                       <property name="border">5</property>
-                      <property name="flag">wxALL</property>
-                      <property name="proportion">0</property>
-                      <object class="wxRadioButton" expanded="true">
+                      <property name="flag">wxALL|wxEXPAND</property>
+                      <property name="proportion">1</property>
+                      <object class="wxChoice" expanded="true">
                         <property name="BottomDockable">1</property>
                         <property name="LeftDockable">1</property>
                         <property name="RightDockable">1</property>
@@ -1519,6 +1520,7 @@
                         <property name="caption"></property>
                         <property name="caption_visible">1</property>
                         <property name="center_pane">0</property>
+                        <property name="choices">&quot;不显示&quot; &quot;拼音&quot; &quot;日文假名&quot;</property>
                         <property name="close_button">1</property>
                         <property name="context_help"></property>
                         <property name="context_menu">1</property>
@@ -1534,7 +1536,6 @@
                         <property name="gripper">0</property>
                         <property name="hidden">0</property>
                         <property name="id">wxID_ANY</property>
-                        <property name="label">不显示</property>
                         <property name="max_size"></property>
                         <property name="maximize_button">0</property>
                         <property name="maximum_size"></property>
@@ -1542,72 +1543,7 @@
                         <property name="minimize_button">0</property>
                         <property name="minimum_size"></property>
                         <property name="moveable">1</property>
-                        <property name="name">gift_pron_none_radio</property>
-                        <property name="pane_border">1</property>
-                        <property name="pane_position"></property>
-                        <property name="pane_size"></property>
-                        <property name="permission">protected</property>
-                        <property name="pin_button">1</property>
-                        <property name="pos"></property>
-                        <property name="resize">Resizable</property>
-                        <property name="show">1</property>
-                        <property name="size"></property>
-                        <property name="style">wxRB_GROUP</property>
-                        <property name="subclass">; ; forward_declare</property>
-                        <property name="toolbar_pane">0</property>
-                        <property name="tooltip"></property>
-                        <property name="validator_data_type"></property>
-                        <property name="validator_style">wxFILTER_NONE</property>
-                        <property name="validator_type">wxDefaultValidator</property>
-                        <property name="validator_variable"></property>
-                        <property name="value">1</property>
-                        <property name="window_extra_style"></property>
-                        <property name="window_name"></property>
-                        <property name="window_style"></property>
-                      </object>
-                    </object>
-                    <object class="sizeritem" expanded="true">
-                      <property name="border">5</property>
-                      <property name="flag">wxALL</property>
-                      <property name="proportion">0</property>
-                      <object class="wxRadioButton" expanded="true">
-                        <property name="BottomDockable">1</property>
-                        <property name="LeftDockable">1</property>
-                        <property name="RightDockable">1</property>
-                        <property name="TopDockable">1</property>
-                        <property name="aui_layer"></property>
-                        <property name="aui_name"></property>
-                        <property name="aui_position"></property>
-                        <property name="aui_row"></property>
-                        <property name="best_size"></property>
-                        <property name="bg"></property>
-                        <property name="caption"></property>
-                        <property name="caption_visible">1</property>
-                        <property name="center_pane">0</property>
-                        <property name="close_button">1</property>
-                        <property name="context_help"></property>
-                        <property name="context_menu">1</property>
-                        <property name="default_pane">0</property>
-                        <property name="dock">Dock</property>
-                        <property name="dock_fixed">0</property>
-                        <property name="docking">Left</property>
-                        <property name="drag_accept_files">0</property>
-                        <property name="enabled">1</property>
-                        <property name="fg"></property>
-                        <property name="floatable">1</property>
-                        <property name="font"></property>
-                        <property name="gripper">0</property>
-                        <property name="hidden">0</property>
-                        <property name="id">wxID_ANY</property>
-                        <property name="label">拼音</property>
-                        <property name="max_size"></property>
-                        <property name="maximize_button">0</property>
-                        <property name="maximum_size"></property>
-                        <property name="min_size"></property>
-                        <property name="minimize_button">0</property>
-                        <property name="minimum_size"></property>
-                        <property name="moveable">1</property>
-                        <property name="name">gift_pron_pinyin_radio</property>
+                        <property name="name">gift_pron_choice</property>
                         <property name="pane_border">1</property>
                         <property name="pane_position"></property>
                         <property name="pane_size"></property>
@@ -1615,6 +1551,7 @@
                         <property name="pin_button">1</property>
                         <property name="pos"></property>
                         <property name="resize">Resizable</property>
+                        <property name="selection">0</property>
                         <property name="show">1</property>
                         <property name="size"></property>
                         <property name="style"></property>
@@ -1625,72 +1562,6 @@
                         <property name="validator_style">wxFILTER_NONE</property>
                         <property name="validator_type">wxDefaultValidator</property>
                         <property name="validator_variable"></property>
-                        <property name="value">0</property>
-                        <property name="window_extra_style"></property>
-                        <property name="window_name"></property>
-                        <property name="window_style"></property>
-                      </object>
-                    </object>
-                    <object class="sizeritem" expanded="true">
-                      <property name="border">5</property>
-                      <property name="flag">wxALL</property>
-                      <property name="proportion">0</property>
-                      <object class="wxRadioButton" expanded="true">
-                        <property name="BottomDockable">1</property>
-                        <property name="LeftDockable">1</property>
-                        <property name="RightDockable">1</property>
-                        <property name="TopDockable">1</property>
-                        <property name="aui_layer"></property>
-                        <property name="aui_name"></property>
-                        <property name="aui_position"></property>
-                        <property name="aui_row"></property>
-                        <property name="best_size"></property>
-                        <property name="bg"></property>
-                        <property name="caption"></property>
-                        <property name="caption_visible">1</property>
-                        <property name="center_pane">0</property>
-                        <property name="close_button">1</property>
-                        <property name="context_help"></property>
-                        <property name="context_menu">1</property>
-                        <property name="default_pane">0</property>
-                        <property name="dock">Dock</property>
-                        <property name="dock_fixed">0</property>
-                        <property name="docking">Left</property>
-                        <property name="drag_accept_files">0</property>
-                        <property name="enabled">1</property>
-                        <property name="fg"></property>
-                        <property name="floatable">1</property>
-                        <property name="font"></property>
-                        <property name="gripper">0</property>
-                        <property name="hidden">0</property>
-                        <property name="id">wxID_ANY</property>
-                        <property name="label">日文假名</property>
-                        <property name="max_size"></property>
-                        <property name="maximize_button">0</property>
-                        <property name="maximum_size"></property>
-                        <property name="min_size"></property>
-                        <property name="minimize_button">0</property>
-                        <property name="minimum_size"></property>
-                        <property name="moveable">1</property>
-                        <property name="name">gift_pron_kana_radio</property>
-                        <property name="pane_border">1</property>
-                        <property name="pane_position"></property>
-                        <property name="pane_size"></property>
-                        <property name="permission">protected</property>
-                        <property name="pin_button">1</property>
-                        <property name="pos"></property>
-                        <property name="resize">Resizable</property>
-                        <property name="show">1</property>
-                        <property name="size"></property>
-                        <property name="style"></property>
-                        <property name="subclass">; ; forward_declare</property>
-                        <property name="toolbar_pane">0</property>
-                        <property name="tooltip"></property>
-                        <property name="validator_data_type"></property>
-                        <property name="validator_style">wxFILTER_NONE</property>
-                        <property name="validator_type">wxDefaultValidator</property>
-                        <property name="validator_variable"></property>
-                        <property name="value">0</property>
                         <property name="window_extra_style"></property>
                         <property name="window_name"></property>
                         <property name="window_style"></property>
@@ -2079,6 +1950,7 @@
                 <property name="window_extra_style"></property>
                 <property name="window_name"></property>
                 <property name="window_style"></property>
+                <event name="OnButtonClick">_on_ok</event>
               </object>
             </object>
             <object class="sizeritem" expanded="true">
@@ -2153,6 +2025,7 @@
                 <property name="window_extra_style"></property>
                 <property name="window_name"></property>
                 <property name="window_style"></property>
+                <event name="OnButtonClick">_on_cancel</event>
               </object>
             </object>
           </object>
diff --git a/plugins/native-ui/designer/ui_base.py b/plugins/native-ui/designer/ui_base.py
index 60d6929..c2fb0d9 100644
--- a/plugins/native-ui/designer/ui_base.py
+++ b/plugins/native-ui/designer/ui_base.py
@@ -64,7 +64,7 @@ class RoomFrameBase ( wx.Frame ):
         self.paid_panel.SetSizer( bSizer4 )
         self.paid_panel.Layout()
         bSizer4.Fit( self.paid_panel )
-        self.console_notebook.AddPage( self.paid_panel, u"付费消息", False )
+        self.console_notebook.AddPage( self.paid_panel, u"付费消息", True )
         self.super_chat_panel = wx.Panel( self.console_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
         bSizer5 = wx.BoxSizer( wx.VERTICAL )
 
@@ -115,7 +115,7 @@ class RoomFrameBase ( wx.Frame ):
         self.statistics_panel.SetSizer( bSizer7 )
         self.statistics_panel.Layout()
         bSizer7.Fit( self.statistics_panel )
-        self.console_notebook.AddPage( self.statistics_panel, u"统计", True )
+        self.console_notebook.AddPage( self.statistics_panel, u"统计", False )
 
         bSizer1.Add( self.console_notebook, 1, wx.EXPAND, 5 )
 
@@ -160,7 +160,7 @@ class RoomFrameBase ( wx.Frame ):
 class RoomConfigDialogBase ( wx.Dialog ):
 
     def __init__( self, parent ):
-        wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"房间设置", pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.DEFAULT_DIALOG_STYLE )
+        wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"房间设置", pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.DEFAULT_DIALOG_STYLE|wx.DIALOG_NO_PARENT )
 
         self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
 
@@ -177,7 +177,7 @@ class RoomConfigDialogBase ( wx.Dialog ):
 
         fgSizer1.Add( self.opacity_label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, 5 )
 
-        self.opacity_slider = wx.Slider( sbSizer1.GetStaticBox(), wx.ID_ANY, 100, 10, 100, wx.DefaultPosition, wx.Size( -1,-1 ), wx.SL_HORIZONTAL )
+        self.opacity_slider = wx.Slider( sbSizer1.GetStaticBox(), wx.ID_ANY, 100, 10, 100, wx.DefaultPosition, wx.Size( 200,-1 ), wx.SL_HORIZONTAL )
         fgSizer1.Add( self.opacity_slider, 1, wx.ALL|wx.EXPAND, 5 )
 
         self.auto_translate_label = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"自动翻译弹幕到日语", wx.DefaultPosition, wx.DefaultSize, 0 )
@@ -188,22 +188,17 @@ class RoomConfigDialogBase ( wx.Dialog ):
         self.auto_translate_check = wx.CheckBox( sbSizer1.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
         fgSizer1.Add( self.auto_translate_check, 1, wx.ALL|wx.EXPAND, 5 )
 
-        self.auto_translate_label1 = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"标注打赏用户名读音", wx.DefaultPosition, wx.DefaultSize, 0 )
-        self.auto_translate_label1.Wrap( -1 )
+        self.gift_pron_label = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"标注打赏用户名读音", wx.DefaultPosition, wx.DefaultSize, 0 )
+        self.gift_pron_label.Wrap( -1 )
 
-        fgSizer1.Add( self.auto_translate_label1, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, 5 )
+        fgSizer1.Add( self.gift_pron_label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, 5 )
 
         bSizer2 = wx.BoxSizer( wx.HORIZONTAL )
 
-        self.gift_pron_none_radio = wx.RadioButton( sbSizer1.GetStaticBox(), wx.ID_ANY, u"不显示", wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP )
-        self.gift_pron_none_radio.SetValue( True )
-        bSizer2.Add( self.gift_pron_none_radio, 0, wx.ALL, 5 )
-
-        self.gift_pron_pinyin_radio = wx.RadioButton( sbSizer1.GetStaticBox(), wx.ID_ANY, u"拼音", wx.DefaultPosition, wx.DefaultSize, 0 )
-        bSizer2.Add( self.gift_pron_pinyin_radio, 0, wx.ALL, 5 )
-
-        self.gift_pron_kana_radio = wx.RadioButton( sbSizer1.GetStaticBox(), wx.ID_ANY, u"日文假名", wx.DefaultPosition, wx.DefaultSize, 0 )
-        bSizer2.Add( self.gift_pron_kana_radio, 0, wx.ALL, 5 )
+        gift_pron_choiceChoices = [ u"不显示", u"拼音", u"日文假名" ]
+        self.gift_pron_choice = wx.Choice( sbSizer1.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, gift_pron_choiceChoices, 0 )
+        self.gift_pron_choice.SetSelection( 0 )
+        bSizer2.Add( self.gift_pron_choice, 1, wx.ALL|wx.EXPAND, 5 )
 
 
         fgSizer1.Add( bSizer2, 1, wx.ALL|wx.EXPAND, 5 )
@@ -250,12 +245,12 @@ class RoomConfigDialogBase ( wx.Dialog ):
 
         bSizer3.Add( ( 0, 0), 1, wx.EXPAND, 5 )
 
-        self.ok_button = wx.Button( self, wx.ID_ANY, u"确定", wx.DefaultPosition, wx.DefaultSize, 0 )
+        self.ok_button = wx.Button( self, wx.ID_OK, u"确定", wx.DefaultPosition, wx.DefaultSize, 0 )
 
         self.ok_button.SetDefault()
         bSizer3.Add( self.ok_button, 0, wx.ALL, 5 )
 
-        self.cancel_button = wx.Button( self, wx.ID_ANY, u"取消", wx.DefaultPosition, wx.DefaultSize, 0 )
+        self.cancel_button = wx.Button( self, wx.ID_CANCEL, u"取消", wx.DefaultPosition, wx.DefaultSize, 0 )
         bSizer3.Add( self.cancel_button, 0, wx.ALL, 5 )
 
 
@@ -268,7 +263,23 @@ class RoomConfigDialogBase ( wx.Dialog ):
 
         self.Centre( wx.BOTH )
 
+        # Connect Events
+        self.opacity_slider.Bind( wx.EVT_SLIDER, self._on_opacity_slider_change )
+        self.ok_button.Bind( wx.EVT_BUTTON, self._on_ok )
+        self.cancel_button.Bind( wx.EVT_BUTTON, self._on_cancel )
+
     def __del__( self ):
         pass
 
 
+    # Virtual event handlers, override them in your derived class
+    def _on_opacity_slider_change( self, event ):
+        event.Skip()
+
+    def _on_ok( self, event ):
+        event.Skip()
+
+    def _on_cancel( self, event ):
+        event.Skip()
+
+
diff --git a/plugins/native-ui/main.pyw b/plugins/native-ui/main.pyw
index c4a29be..0b23921 100755
--- a/plugins/native-ui/main.pyw
+++ b/plugins/native-ui/main.pyw
@@ -29,6 +29,7 @@ async def init():
     init_signal_handlers()
 
     init_logging()
+    config.init()
 
     await blcsdk.init()
     if not blcsdk.is_sdk_version_compatible():
diff --git a/plugins/native-ui/ui/app.py b/plugins/native-ui/ui/app.py
index 2b4e096..b9c701f 100644
--- a/plugins/native-ui/ui/app.py
+++ b/plugins/native-ui/ui/app.py
@@ -7,6 +7,7 @@ import wxasync
 
 import blcsdk.models as sdk_models
 import listener
+import ui.room_config_dialog
 import ui.room_frame
 
 logger = logging.getLogger('native-ui.' + __name__)
@@ -25,12 +26,14 @@ class App(wxasync.WxAsyncApp):
         self.SetExitOnFrameDelete(False)
 
         self._key_room_frame_dict: Dict[sdk_models.RoomKey, ui.room_frame.RoomFrame] = {}
+        self._room_config_dialog: Optional[ui.room_config_dialog.RoomConfigDialog] = None
 
     def OnInit(self):
         pub.subscribe(self._on_add_room, 'add_room')
         pub.subscribe(self._on_del_room, 'del_room')
         pub.subscribe(self._on_room_frame_close, 'room_frame_close')
         pub.subscribe(self._on_open_admin_ui, 'open_admin_ui')
+        pub.subscribe(self._on_open_room_config_dialog, 'open_room_config_dialog')
         return True
 
     def _on_add_room(self, room_key: sdk_models.RoomKey):
@@ -51,3 +54,8 @@ class App(wxasync.WxAsyncApp):
     def _on_open_admin_ui(self):
         for room in listener.iter_rooms():
             self._on_add_room(room.room_key)
+
+    def _on_open_room_config_dialog(self):
+        if self._room_config_dialog is None or self._room_config_dialog.IsBeingDeleted():
+            self._room_config_dialog = ui.room_config_dialog.RoomConfigDialog(None)
+        self._room_config_dialog.Show()
diff --git a/plugins/native-ui/ui/room_config_dialog.py b/plugins/native-ui/ui/room_config_dialog.py
new file mode 100644
index 0000000..4e7605b
--- /dev/null
+++ b/plugins/native-ui/ui/room_config_dialog.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+import copy
+import logging
+from typing import *
+
+import pubsub.pub as pub
+import wx
+
+import config
+import designer.ui_base
+
+logger = logging.getLogger('native-ui.' + __name__)
+
+
+class RoomConfigDialog(designer.ui_base.RoomConfigDialogBase):
+    _GIFT_PRON_CHOICES = ('', 'pinyin', 'kana')
+
+    def __init__(self, parent):
+        super().__init__(parent)
+
+        self._new_cfg_cache: Optional[config.AppConfig] = None
+
+    def TransferDataToWindow(self):
+        cfg = config.get_config()
+        url_params: dict = cfg.chat_url_params
+
+        self.opacity_slider.SetValue(cfg.room_opacity)
+        self.auto_translate_check.SetValue(self._to_bool(url_params.get('autoTranslate', 'false')))
+        try:
+            gift_pron_index = self._GIFT_PRON_CHOICES.index(url_params.get('giftUsernamePronunciation', ''))
+        except ValueError:
+            gift_pron_index = 0
+        self.gift_pron_choice.SetSelection(gift_pron_index)
+        self.min_gift_price_edit.SetValue(url_params.get('minGiftPrice', '0'))
+        self.block_gift_danmaku_check.SetValue(self._to_bool(url_params.get('blockGiftDanmaku', 'true')))
+
+        return super().TransferDataToWindow()
+
+    def _on_ok(self, event):
+        try:
+            self._new_cfg_cache = self._create_config_from_window()
+        except Exception as e:
+            logger.exception('_create_config_from_window failed:')
+            wx.MessageBox(str(e), '应用设置失败', wx.OK | wx.ICON_ERROR | wx.CENTRE, self)
+            return
+
+        if (
+            self._new_cfg_cache.is_url_params_changed(config.get_config())
+            and wx.MessageBox(
+                '修改部分设置需要刷新浏览器,是否继续?', '提示', wx.YES_NO | wx.CENTRE, self
+            ) != wx.YES
+        ):
+            return
+        super()._on_ok(event)
+
+    def _create_config_from_window(self):
+        cfg = copy.deepcopy(config.get_config())
+
+        cfg.room_opacity = self.opacity_slider.GetValue()
+        url_params = {
+            'autoTranslate': self._bool_to_str(self.auto_translate_check.GetValue()),
+            'giftUsernamePronunciation': self._GIFT_PRON_CHOICES[self.gift_pron_choice.GetSelection()],
+            'minGiftPrice': self.min_gift_price_edit.GetValue(),
+            'blockGiftDanmaku': self._bool_to_str(self.block_gift_danmaku_check.GetValue()),
+        }
+        cfg.chat_url_params.update(url_params)
+        cfg.paid_url_params.update(url_params)
+
+        return cfg
+
+    def TransferDataFromWindow(self):
+        if self._new_cfg_cache is None:
+            logger.warning('_new_cfg_cache is None')
+            return False
+
+        config.set_config(self._new_cfg_cache)
+        wx.CallAfter(self._new_cfg_cache.save, config.SAVE_CONFIG_PATH)
+
+        return super().TransferDataFromWindow()
+
+    def _on_cancel(self, event):
+        pub.sendMessage('room_config_dialog_cancel')
+        super()._on_cancel(event)
+
+    @staticmethod
+    def _to_bool(value):
+        if isinstance(value, str):
+            return value.lower() not in ('false', 'no', 'off', '0', '')
+        return bool(value)
+
+    @staticmethod
+    def _bool_to_str(value):
+        return 'true' if value else 'false'
+
+    def _on_opacity_slider_change(self, event):
+        pub.sendMessage('preview_room_opacity', room_opacity=self.opacity_slider.GetValue())
diff --git a/plugins/native-ui/ui/room_frame.py b/plugins/native-ui/ui/room_frame.py
index 985dde4..b18fbc3 100644
--- a/plugins/native-ui/ui/room_frame.py
+++ b/plugins/native-ui/ui/room_frame.py
@@ -10,6 +10,7 @@ import xlsxwriter.exceptions
 
 import blcsdk
 import blcsdk.models as sdk_models
+import config
 import designer.ui_base
 import listener
 
@@ -25,10 +26,7 @@ class RoomFrame(designer.ui_base.RoomFrameBase):
         room_str = str(room.room_id) if room is not None else str(self._room_key)
         self.SetTitle(f'blivechat - 房间 {room_str}')
 
-        room_params = {'minGiftPrice': 0, 'showGiftName': 'true'}
-        self.chat_web_view.LoadURL(self._get_room_url(room_params))
-        room_params['showDanmaku'] = 'false'
-        self.paid_web_view.LoadURL(self._get_room_url(room_params))
+        self._apply_config(True)
 
         self.super_chat_list.AppendColumn('时间', width=50)
         self.super_chat_list.AppendColumn('用户名', width=120)
@@ -53,6 +51,9 @@ class RoomFrame(designer.ui_base.RoomFrameBase):
         for index in room.uid_paid_user_dict:
             self._on_uid_paid_user_dict_change(room, room.uid_paid_user_dict, index, True)
 
+        pub.subscribe(self._on_preview_room_opacity, 'preview_room_opacity')
+        pub.subscribe(self._on_room_config_dialog_cancel, 'room_config_dialog_cancel')
+        pub.subscribe(self._on_config_change, 'config_change')
         pub.subscribe(self._on_super_chats_change, 'room_data_change.super_chats')
         pub.subscribe(self._on_gifts_change, 'room_data_change.gifts')
         pub.subscribe(self._on_uid_paid_user_dict_change, 'room_data_change.uid_paid_user_dict')
@@ -60,22 +61,8 @@ class RoomFrame(designer.ui_base.RoomFrameBase):
         pub.subscribe(self._on_simple_statistics_change, 'room_data_change.interact_uids')
         pub.subscribe(self._on_simple_statistics_change, 'room_data_change.total_paid_price')
 
-    def _get_room_url(self, params: dict):
-        params = params.copy()
-        params['roomKeyType'] = self._room_key.type.value
-        params['relayMessagesByServer'] = 'true'
-
-        query = '&'.join(
-            f'{urllib.parse.quote_plus(key)}={urllib.parse.quote_plus(str(value))}'
-            for key, value in params.items()
-        )
-        blc_port = blcsdk.get_blc_port()
-        encoded_room_key_value = urllib.parse.quote_plus(str(self._room_key.value))
-        url = f'http://localhost:{blc_port}/room/{encoded_room_key_value}?{query}'
-        return url
-
     #
-    # UI事件
+    # 本窗口UI事件
     #
 
     def _on_close(self, event):
@@ -83,9 +70,7 @@ class RoomFrame(designer.ui_base.RoomFrameBase):
         super()._on_close(event)
 
     def _on_config_button_click(self, event):
-        # TODO WIP
-        dialog = designer.ui_base.RoomConfigDialogBase(self)
-        dialog.Show()
+        pub.sendMessage('open_room_config_dialog')
 
     def _on_stay_on_top_button_toggle(self, event: wx.CommandEvent):
         style = self.GetWindowStyle()
@@ -161,6 +146,51 @@ class RoomFrame(designer.ui_base.RoomFrameBase):
         for row in range(row_num):
             yield [list_ctrl.GetItemText(row, col) for col in range(col_num)]
 
+    #
+    # 配置事件
+    #
+
+    def _on_preview_room_opacity(self, room_opacity):
+        self._set_opacity(room_opacity)
+
+    def _set_opacity(self, opacity):
+        opacity = min(max(opacity, 10), 100)
+        alpha = round(opacity * wx.IMAGE_ALPHA_OPAQUE / 100)
+        return self.SetTransparent(alpha)
+
+    def _on_room_config_dialog_cancel(self):
+        cfg = config.get_config()
+        self._set_opacity(cfg.room_opacity)
+
+    def _on_config_change(self, new_config: config.AppConfig, old_config: config.AppConfig):
+        self._apply_config(new_config.is_url_params_changed(old_config))
+
+    def _apply_config(self, reload_web_views):
+        cfg = config.get_config()
+        self._set_opacity(cfg.room_opacity)
+        if reload_web_views:
+            self.chat_web_view.LoadURL(self._get_room_url(cfg.chat_url_params))
+            self.paid_web_view.LoadURL(self._get_room_url(cfg.paid_url_params, {'showDanmaku': 'false'}))
+
+    def _get_room_url(self, params: dict, override_params: Optional[dict] = None):
+        if override_params is None:
+            override_params = {}
+        params = {
+            **params,
+            'roomKeyType': self._room_key.type.value,
+            'relayMessagesByServer': 'true',
+            **override_params,
+        }
+
+        query = '&'.join(
+            f'{urllib.parse.quote_plus(key)}={urllib.parse.quote_plus(str(value))}'
+            for key, value in params.items()
+        )
+        blc_port = blcsdk.get_blc_port()
+        encoded_room_key_value = urllib.parse.quote_plus(str(self._room_key.value))
+        url = f'http://localhost:{blc_port}/room/{encoded_room_key_value}?{query}'
+        return url
+
     #
     # 模型事件
     #