forked from Elara6331/itd
		
	Add comments to gui
This commit is contained in:
		| @@ -11,20 +11,28 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func guiErr(err error, msg string, parent fyne.Window) { | func guiErr(err error, msg string, parent fyne.Window) { | ||||||
|  | 	// Create new label containing message | ||||||
| 	msgLbl := widget.NewLabel(msg) | 	msgLbl := widget.NewLabel(msg) | ||||||
|  | 	// Text formatting settings | ||||||
| 	msgLbl.Wrapping = fyne.TextWrapWord | 	msgLbl.Wrapping = fyne.TextWrapWord | ||||||
| 	msgLbl.Alignment = fyne.TextAlignCenter | 	msgLbl.Alignment = fyne.TextAlignCenter | ||||||
|  | 	// Create new rectangle to set the size of the dialog | ||||||
| 	rect := canvas.NewRectangle(color.Transparent) | 	rect := canvas.NewRectangle(color.Transparent) | ||||||
|  | 	// Set minimum size of rectangle to 350x0 | ||||||
| 	rect.SetMinSize(fyne.NewSize(350, 0)) | 	rect.SetMinSize(fyne.NewSize(350, 0)) | ||||||
|  | 	// Create new container containing message and rectangle | ||||||
| 	content := container.NewVBox( | 	content := container.NewVBox( | ||||||
| 		msgLbl, | 		msgLbl, | ||||||
| 		rect, | 		rect, | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		// Create new label containing error text | ||||||
| 		errLbl := widget.NewLabel(err.Error()) | 		errLbl := widget.NewLabel(err.Error()) | ||||||
|  | 		// Create new dropdown containing error label | ||||||
| 		content.Add(widget.NewAccordion( | 		content.Add(widget.NewAccordion( | ||||||
| 			widget.NewAccordionItem("More Details", errLbl), | 			widget.NewAccordionItem("More Details", errLbl), | ||||||
| 		)) | 		)) | ||||||
| 	} | 	} | ||||||
|  | 	// Show error dialog | ||||||
| 	dialog.NewCustom("Error", "Ok", content, parent).Show() | 	dialog.NewCustom("Error", "Ok", content, parent).Show() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,7 +22,9 @@ func infoTab(parent fyne.Window) *fyne.Container { | |||||||
| 		canvas.NewRectangle(color.Transparent), | 		canvas.NewRectangle(color.Transparent), | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	// Create label for heart rate | ||||||
| 	heartRateLbl := newText("0 BPM", 24) | 	heartRateLbl := newText("0 BPM", 24) | ||||||
|  | 	// Creae container to store heart rate section | ||||||
| 	heartRate := container.NewVBox( | 	heartRate := container.NewVBox( | ||||||
| 		newText("Heart Rate", 12), | 		newText("Heart Rate", 12), | ||||||
| 		heartRateLbl, | 		heartRateLbl, | ||||||
| @@ -30,12 +32,17 @@ func infoTab(parent fyne.Window) *fyne.Container { | |||||||
| 	) | 	) | ||||||
| 	infoLayout.Add(heartRate) | 	infoLayout.Add(heartRate) | ||||||
|  |  | ||||||
|  | 	// Watch for heart rate updates | ||||||
| 	go watch(types.ReqTypeWatchHeartRate, func(data interface{}) { | 	go watch(types.ReqTypeWatchHeartRate, func(data interface{}) { | ||||||
|  | 		// Change text of heart rate label | ||||||
| 		heartRateLbl.Text = fmt.Sprintf("%d BPM", int(data.(float64))) | 		heartRateLbl.Text = fmt.Sprintf("%d BPM", int(data.(float64))) | ||||||
|  | 		// Refresh label | ||||||
| 		heartRateLbl.Refresh() | 		heartRateLbl.Refresh() | ||||||
| 	}, parent) | 	}, parent) | ||||||
|  |  | ||||||
|  | 	// Create label for battery level | ||||||
| 	battLevelLbl := newText("0%", 24) | 	battLevelLbl := newText("0%", 24) | ||||||
|  | 	// Create container to store battery level section | ||||||
| 	battLevel := container.NewVBox( | 	battLevel := container.NewVBox( | ||||||
| 		newText("Battery Level", 12), | 		newText("Battery Level", 12), | ||||||
| 		battLevelLbl, | 		battLevelLbl, | ||||||
| @@ -43,6 +50,7 @@ func infoTab(parent fyne.Window) *fyne.Container { | |||||||
| 	) | 	) | ||||||
| 	infoLayout.Add(battLevel) | 	infoLayout.Add(battLevel) | ||||||
|  |  | ||||||
|  | 	// Watch for changes in battery level | ||||||
| 	go watch(types.ReqTypeWatchBattLevel, func(data interface{}) { | 	go watch(types.ReqTypeWatchBattLevel, func(data interface{}) { | ||||||
| 		battLevelLbl.Text = fmt.Sprintf("%d%%", int(data.(float64))) | 		battLevelLbl.Text = fmt.Sprintf("%d%%", int(data.(float64))) | ||||||
| 		battLevelLbl.Refresh() | 		battLevelLbl.Refresh() | ||||||
|   | |||||||
| @@ -8,9 +8,12 @@ import ( | |||||||
| var SockPath = "/tmp/itd/socket" | var SockPath = "/tmp/itd/socket" | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
|  | 	// Create new app | ||||||
| 	a := app.New() | 	a := app.New() | ||||||
|  | 	// Create new window with title "itgui" | ||||||
| 	window := a.NewWindow("itgui") | 	window := a.NewWindow("itgui") | ||||||
|  |  | ||||||
|  | 	// Create new app tabs container | ||||||
| 	tabs := container.NewAppTabs( | 	tabs := container.NewAppTabs( | ||||||
| 		container.NewTabItem("Info", infoTab(window)), | 		container.NewTabItem("Info", infoTab(window)), | ||||||
| 		container.NewTabItem("Notify", notifyTab(window)), | 		container.NewTabItem("Notify", notifyTab(window)), | ||||||
| @@ -18,6 +21,8 @@ func main() { | |||||||
| 		container.NewTabItem("Upgrade", upgradeTab(window)), | 		container.NewTabItem("Upgrade", upgradeTab(window)), | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	// Set tabs as window content | ||||||
| 	window.SetContent(tabs) | 	window.SetContent(tabs) | ||||||
|  | 	// Show window and run app | ||||||
| 	window.ShowAndRun() | 	window.ShowAndRun() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,18 +12,23 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func notifyTab(parent fyne.Window) *fyne.Container { | func notifyTab(parent fyne.Window) *fyne.Container { | ||||||
|  | 	// Create new entry for notification title | ||||||
| 	titleEntry := widget.NewEntry() | 	titleEntry := widget.NewEntry() | ||||||
| 	titleEntry.SetPlaceHolder("Title") | 	titleEntry.SetPlaceHolder("Title") | ||||||
|  |  | ||||||
|  | 	// Create multiline entry for notification body | ||||||
| 	bodyEntry := widget.NewMultiLineEntry() | 	bodyEntry := widget.NewMultiLineEntry() | ||||||
| 	bodyEntry.SetPlaceHolder("Body") | 	bodyEntry.SetPlaceHolder("Body") | ||||||
|  |  | ||||||
|  | 	// Create new button to send notification | ||||||
| 	sendBtn := widget.NewButton("Send", func() { | 	sendBtn := widget.NewButton("Send", func() { | ||||||
|  | 		// Dial itd UNIX socket | ||||||
| 		conn, err := net.Dial("unix", SockPath) | 		conn, err := net.Dial("unix", SockPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			guiErr(err, "Error dialing socket", parent) | 			guiErr(err, "Error dialing socket", parent) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		// Encode notify request on connection | ||||||
| 		json.NewEncoder(conn).Encode(types.Request{ | 		json.NewEncoder(conn).Encode(types.Request{ | ||||||
| 			Type: types.ReqTypeNotify, | 			Type: types.ReqTypeNotify, | ||||||
| 			Data: types.ReqDataNotify{ | 			Data: types.ReqDataNotify{ | ||||||
| @@ -33,6 +38,7 @@ func notifyTab(parent fyne.Window) *fyne.Container { | |||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	// Return new container containing all elements | ||||||
| 	return container.NewVBox( | 	return container.NewVBox( | ||||||
| 		layout.NewSpacer(), | 		layout.NewSpacer(), | ||||||
| 		titleEntry, | 		titleEntry, | ||||||
|   | |||||||
| @@ -13,24 +13,30 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func timeTab(parent fyne.Window) *fyne.Container { | func timeTab(parent fyne.Window) *fyne.Container { | ||||||
|  | 	// Create new entry for time string | ||||||
| 	timeEntry := widget.NewEntry() | 	timeEntry := widget.NewEntry() | ||||||
|  | 	// Set text to current time formatter properly | ||||||
| 	timeEntry.SetText(time.Now().Format(time.RFC1123)) | 	timeEntry.SetText(time.Now().Format(time.RFC1123)) | ||||||
|  |  | ||||||
|  | 	// Create button to set current time | ||||||
| 	currentBtn := widget.NewButton("Set Current", func() { | 	currentBtn := widget.NewButton("Set Current", func() { | ||||||
| 		timeEntry.SetText(time.Now().Format(time.RFC1123)) | 		timeEntry.SetText(time.Now().Format(time.RFC1123)) | ||||||
| 		setTime(true) | 		setTime(true) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	// Create button to set time inside entry | ||||||
| 	timeBtn := widget.NewButton("Set", func() { | 	timeBtn := widget.NewButton("Set", func() { | ||||||
|  | 		// Parse time as RFC1123 string | ||||||
| 		parsedTime, err := time.Parse(time.RFC1123, timeEntry.Text) | 		parsedTime, err := time.Parse(time.RFC1123, timeEntry.Text) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			guiErr(err, "Error parsing time string", parent) | 			guiErr(err, "Error parsing time string", parent) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		// Set time to parsed time | ||||||
| 		setTime(false, parsedTime) | 		setTime(false, parsedTime) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	// Return new container with all elements centered | ||||||
| 	return container.NewVBox( | 	return container.NewVBox( | ||||||
| 		layout.NewSpacer(), | 		layout.NewSpacer(), | ||||||
| 		timeEntry, | 		timeEntry, | ||||||
| @@ -43,17 +49,25 @@ func timeTab(parent fyne.Window) *fyne.Container { | |||||||
| // setTime sets the first element in the variadic parameter | // setTime sets the first element in the variadic parameter | ||||||
| // if current is false, otherwise, it sets the current time. | // if current is false, otherwise, it sets the current time. | ||||||
| func setTime(current bool, t ...time.Time) error { | func setTime(current bool, t ...time.Time) error { | ||||||
|  | 	// Dial UNIX socket | ||||||
| 	conn, err := net.Dial("unix", SockPath) | 	conn, err := net.Dial("unix", SockPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	defer conn.Close() | ||||||
| 	var data string | 	var data string | ||||||
|  | 	// If current is true, use the string "now" | ||||||
|  | 	// otherwise, use the formatted time from the | ||||||
|  | 	// first element in the variadic parameter. | ||||||
|  | 	// "now" is more accurate than formatting | ||||||
|  | 	// current time as only seconds are preserved | ||||||
|  | 	// in that case. | ||||||
| 	if current { | 	if current { | ||||||
| 		data = "now" | 		data = "now" | ||||||
| 	} else { | 	} else { | ||||||
| 		data = t[0].Format(time.RFC3339) | 		data = t[0].Format(time.RFC3339) | ||||||
| 	} | 	} | ||||||
| 	defer conn.Close() | 	// Encode SetTime request with above data | ||||||
| 	err = json.NewEncoder(conn).Encode(types.Request{ | 	err = json.NewEncoder(conn).Encode(types.Request{ | ||||||
| 		Type: types.ReqTypeSetTime, | 		Type: types.ReqTypeSetTime, | ||||||
| 		Data: data, | 		Data: data, | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 		initPktPath string | 		initPktPath string | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	// Create archive selection dialog | ||||||
| 	archiveDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | 	archiveDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | ||||||
| 		if e != nil || uc == nil { | 		if e != nil || uc == nil { | ||||||
| 			return | 			return | ||||||
| @@ -30,9 +31,12 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 		uc.Close() | 		uc.Close() | ||||||
| 		archivePath = uc.URI().Path() | 		archivePath = uc.URI().Path() | ||||||
| 	}, parent) | 	}, parent) | ||||||
|  | 	// Limit dialog to .zip files | ||||||
| 	archiveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".zip"})) | 	archiveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".zip"})) | ||||||
|  | 	// Create button to show dialog | ||||||
| 	archiveBtn := widget.NewButton("Select archive (.zip)", archiveDialog.Show) | 	archiveBtn := widget.NewButton("Select archive (.zip)", archiveDialog.Show) | ||||||
|  |  | ||||||
|  | 	// Create firmware selection dialog | ||||||
| 	firmwareDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | 	firmwareDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | ||||||
| 		if e != nil || uc == nil { | 		if e != nil || uc == nil { | ||||||
| 			return | 			return | ||||||
| @@ -40,9 +44,12 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 		uc.Close() | 		uc.Close() | ||||||
| 		fiwmarePath = uc.URI().Path() | 		fiwmarePath = uc.URI().Path() | ||||||
| 	}, parent) | 	}, parent) | ||||||
|  | 	// Limit dialog to .bin files | ||||||
| 	firmwareDialog.SetFilter(storage.NewExtensionFileFilter([]string{".bin"})) | 	firmwareDialog.SetFilter(storage.NewExtensionFileFilter([]string{".bin"})) | ||||||
|  | 	// Create button to show dialog | ||||||
| 	firmwareBtn := widget.NewButton("Select init packet (.bin)", firmwareDialog.Show) | 	firmwareBtn := widget.NewButton("Select init packet (.bin)", firmwareDialog.Show) | ||||||
|  |  | ||||||
|  | 	// Create init packet selection dialog | ||||||
| 	initPktDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | 	initPktDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) { | ||||||
| 		if e != nil || uc == nil { | 		if e != nil || uc == nil { | ||||||
| 			return | 			return | ||||||
| @@ -50,19 +57,25 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 		uc.Close() | 		uc.Close() | ||||||
| 		initPktPath = uc.URI().Path() | 		initPktPath = uc.URI().Path() | ||||||
| 	}, parent) | 	}, parent) | ||||||
|  | 	// Limit dialog to .dat files | ||||||
| 	initPktDialog.SetFilter(storage.NewExtensionFileFilter([]string{".dat"})) | 	initPktDialog.SetFilter(storage.NewExtensionFileFilter([]string{".dat"})) | ||||||
|  | 	// Create button to show dialog | ||||||
| 	initPktBtn := widget.NewButton("Select init packet (.dat)", initPktDialog.Show) | 	initPktBtn := widget.NewButton("Select init packet (.dat)", initPktDialog.Show) | ||||||
|  |  | ||||||
|  | 	// Hide init packet and firmware buttons | ||||||
| 	initPktBtn.Hide() | 	initPktBtn.Hide() | ||||||
| 	firmwareBtn.Hide() | 	firmwareBtn.Hide() | ||||||
|  |  | ||||||
|  | 	// Create dropdown to select upgrade type | ||||||
| 	upgradeTypeSelect := widget.NewSelect([]string{ | 	upgradeTypeSelect := widget.NewSelect([]string{ | ||||||
| 		"Archive", | 		"Archive", | ||||||
| 		"Files", | 		"Files", | ||||||
| 	}, func(s string) { | 	}, func(s string) { | ||||||
|  | 		// Hide all buttons | ||||||
| 		archiveBtn.Hide() | 		archiveBtn.Hide() | ||||||
| 		initPktBtn.Hide() | 		initPktBtn.Hide() | ||||||
| 		firmwareBtn.Hide() | 		firmwareBtn.Hide() | ||||||
|  | 		// Unhide appropriate button(s) | ||||||
| 		switch s { | 		switch s { | ||||||
| 		case "Archive": | 		case "Archive": | ||||||
| 			archiveBtn.Show() | 			archiveBtn.Show() | ||||||
| @@ -71,26 +84,35 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 			firmwareBtn.Show() | 			firmwareBtn.Show() | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  | 	// Select first elemetn | ||||||
| 	upgradeTypeSelect.SetSelectedIndex(0) | 	upgradeTypeSelect.SetSelectedIndex(0) | ||||||
|  |  | ||||||
|  | 	// Create new button to start DFU | ||||||
| 	startBtn := widget.NewButton("Start", func() { | 	startBtn := widget.NewButton("Start", func() { | ||||||
|  | 		// If archive path does not exist and both init packet and firmware paths | ||||||
|  | 		// also do not exist, return error | ||||||
| 		if archivePath == "" && (initPktPath == "" && fiwmarePath == "") { | 		if archivePath == "" && (initPktPath == "" && fiwmarePath == "") { | ||||||
| 			guiErr(nil, "Upgrade requires archive or files selected", parent) | 			guiErr(nil, "Upgrade requires archive or files selected", parent) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// Create new label for byte progress | ||||||
| 		progressLbl := widget.NewLabelWithStyle("0 / 0 B", fyne.TextAlignCenter, fyne.TextStyle{}) | 		progressLbl := widget.NewLabelWithStyle("0 / 0 B", fyne.TextAlignCenter, fyne.TextStyle{}) | ||||||
|  | 		// Create new progress bar | ||||||
| 		progressBar := widget.NewProgressBar() | 		progressBar := widget.NewProgressBar() | ||||||
|  | 		// Create modal dialog containing label and progress bar | ||||||
| 		progressDlg := widget.NewModalPopUp(container.NewVBox( | 		progressDlg := widget.NewModalPopUp(container.NewVBox( | ||||||
| 			layout.NewSpacer(), | 			layout.NewSpacer(), | ||||||
| 			progressLbl, | 			progressLbl, | ||||||
| 			progressBar, | 			progressBar, | ||||||
| 			layout.NewSpacer(), | 			layout.NewSpacer(), | ||||||
| 		), parent.Canvas()) | 		), parent.Canvas()) | ||||||
|  | 		// Resize modal to 300x100 | ||||||
| 		progressDlg.Resize(fyne.NewSize(300, 100)) | 		progressDlg.Resize(fyne.NewSize(300, 100)) | ||||||
|  |  | ||||||
| 		var fwUpgType int | 		var fwUpgType int | ||||||
| 		var files []string | 		var files []string | ||||||
|  | 		// Get appropriate upgrade type and file paths | ||||||
| 		switch upgradeTypeSelect.Selected { | 		switch upgradeTypeSelect.Selected { | ||||||
| 		case "Archive": | 		case "Archive": | ||||||
| 			fwUpgType = types.UpgradeTypeArchive | 			fwUpgType = types.UpgradeTypeArchive | ||||||
| @@ -100,6 +122,7 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 			files = append(files, initPktPath, fiwmarePath) | 			files = append(files, initPktPath, fiwmarePath) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// Dial itd UNIX socket | ||||||
| 		conn, err := net.Dial("unix", SockPath) | 		conn, err := net.Dial("unix", SockPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			guiErr(err, "Error dialing socket", parent) | 			guiErr(err, "Error dialing socket", parent) | ||||||
| @@ -107,6 +130,7 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 		} | 		} | ||||||
| 		defer conn.Close() | 		defer conn.Close() | ||||||
|  |  | ||||||
|  | 		// Encode firmware upgrade request to connection | ||||||
| 		json.NewEncoder(conn).Encode(types.Request{ | 		json.NewEncoder(conn).Encode(types.Request{ | ||||||
| 			Type: types.ReqTypeFwUpgrade, | 			Type: types.ReqTypeFwUpgrade, | ||||||
| 			Data: types.ReqDataFwUpgrade{ | 			Data: types.ReqDataFwUpgrade{ | ||||||
| @@ -115,7 +139,10 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 			}, | 			}, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | 		// Show progress dialog | ||||||
| 		progressDlg.Show() | 		progressDlg.Show() | ||||||
|  | 		// Hide progress dialog after completion | ||||||
|  | 		defer progressDlg.Hide() | ||||||
|  |  | ||||||
| 		scanner := bufio.NewScanner(conn) | 		scanner := bufio.NewScanner(conn) | ||||||
| 		for scanner.Scan() { | 		for scanner.Scan() { | ||||||
| @@ -141,16 +168,17 @@ func upgradeTab(parent fyne.Window) *fyne.Container { | |||||||
| 			if event.Received == event.Total { | 			if event.Received == event.Total { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
|  | 			// Set label text to received / total B | ||||||
| 			progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total)) | 			progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total)) | ||||||
|  | 			// Set progress bar values | ||||||
| 			progressBar.Max = float64(event.Total) | 			progressBar.Max = float64(event.Total) | ||||||
| 			progressBar.Value = float64(event.Received) | 			progressBar.Value = float64(event.Received) | ||||||
|  | 			// Refresh progress bar | ||||||
| 			progressBar.Refresh() | 			progressBar.Refresh() | ||||||
| 		} | 		} | ||||||
| 		conn.Close() |  | ||||||
|  |  | ||||||
| 		progressDlg.Hide() |  | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	// Return container containing all elements | ||||||
| 	return container.NewVBox( | 	return container.NewVBox( | ||||||
| 		layout.NewSpacer(), | 		layout.NewSpacer(), | ||||||
| 		upgradeTypeSelect, | 		upgradeTypeSelect, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user