Автор: Павел Грибов
Raspberri PI: прием и передача сигналов на частоте 433mhz
На даче у меня управление батареями построено на реле Sonoff R3 DIY, которые включаются-выключаются при помощи WiFi. Это было моей ошибкой — управление получилось не очень стабильным. Реле имеют свойство периодически «отваливаться», из-за того что сигнал от роутера слабоват и весь дом покрывает с трудом. Решил освоить управление реле при помощи приёмопередатчиков 433mhz. Приём-передача по слухам гораздо стабильнее. Да и пультики-брелки можно будет прикрутить таким образом для управления уличным освещением. Приятно будет — подъезжаешь вечером на машине и шарахаться в темноте не нужно — с пульта включил свет. Плюс бонусом хочу на первый этаж поставить arduino с приёмником, и часть функционала с raspberry перекинуть на неё, т.к. тупо пинов уже не хватает к малинке.
» Читать далееDart: секунды в строку вида hh:mm:ss
Задача: преобразовать и отобразить секунды в строку часы-минуты-секунды
Решение:
String TimeLeft(diff){
var timestamp = diff.floor();
var hours = (timestamp / 60 / 60).floor();
var minutes = ((timestamp / 60) - (hours * 60)).floor();
var seconds = timestamp % 60;
if (seconds<0){
seconds=0;hours=0;minutes=0;
}
String hs=hours.toString();
hs=hs.length>1?hs:"0"+hs;
String ms=minutes.toString();
ms=ms.length>1?ms:"0"+hs;
String ss=seconds.toString();
ss=ss.length>1?ss:"0"+ss;
return "$hs:$ms:$ss";
}
Бонусом тоже самое на javascript:
function TimeLeft(diff){
var timestamp = Math.floor(diff);
var hours = Math.floor(timestamp / 60 / 60);
var minutes = Math.floor(timestamp / 60) - (hours * 60);
var seconds = timestamp % 60;
if (seconds<0){seconds=0;hours=0;minutes=0;}
h=("0"+hours).slice(-2);
m=("0"+minutes).slice(-2);
s=("0"+seconds).slice(-2);
};
Flutter: использование радиокнопок
Обычно радиокнопки во Flutter обертываю виджетом ListTile. Однако в этом случае получаются слишком большие отступы между кнопками, которые обычными средствами не убираются. В моем варианте, кнопки размещаются просто в Row строке:
enum ChangingTime {t30a,t30,t60,t90,t120,t150,t180}
...
ChangingTime? _chtime = ChangingTime.t30a;
...
Container RadioLine(String title,ChangingTime rvalue){
return
Container(
padding: const EdgeInsets.only(bottom: 0,left: 0,right: 0,top: 0),
height: 20,
child:
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 0,left: 8,right: 8,top: 0),
child: Text(title),
)
),
Radio<ChangingTime>(
value: rvalue,
groupValue: _chtime,
onChanged: (ChangingTime? value) {
setState(() {
_chtime = value;
print(value);
});
},
),
],
)
);
}
...
...
RadioLine("30 минут с автопродлением",ChangingTime.t30a),
new Divider(),
RadioLine("30 минут",ChangingTime.t30),
new Divider(),
RadioLine("1 час",ChangingTime.t60),
Получается вполне приемлимое:

Одно но. В таком случае выбор кнопки будет только по щелчку по переключателю, а не по тексту. Как вариант можно попробовать RadioListTile, но с ним отступы до желаемого минимума мне убрать не удалось.
RadioListTile RadioLine2(String title,ChangingTime rvalue){
return
RadioListTile<ChangingTime>(
title:Text(
title,
),
dense: true,
value: rvalue,
groupValue: _chtime,
visualDensity: VisualDensity(
horizontal: VisualDensity.minimumDensity,
vertical: VisualDensity.minimumDensity,
),
controlAffinity: ListTileControlAffinity.trailing,
onChanged: (ChangingTime? value) {
setState(() {
_chtime = value;
print(value);
});
},
);
}

Flutter: древовидный список с анимацией
Задача: показать двухуровневый список с анимацией отображения выбора второго уровня.

Что сделаем? Отображением займутся два вложенных друг в друга ListView.builder. Отлавливает выбор модели при помощи GestureDetector и при отрисовке второго ListView.builder со списокм автомобилей, показываем только совпадающие с моделью. Также отображение моделей обертываем в AnimatedOpacity для плавного показа.
class _AddAutoState extends State<AddAuto> {
final AddAuto_scaffoldKey = GlobalKey<ScaffoldState>();
bool auto_list_is_loading=false;
List ListAutoes=[];
List FilteredListAutoes=[];
int cur_model_select=0;
void LoadListAutoes(context){
TRequests req=new TRequests();
req.request("GetListVendorsAndModels", jsonEncode({}), (List result){
setState(() {
ListAutoes=result;
FilteredListAutoes=result;
auto_list_is_loading=true;
});
}, (String error){
EasyLoading.showToast(error);
});
}
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => LoadListAutoes(context)); // эвент после того как страница отобразилась - обновим данные по пользователю
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: AddAuto_scaffoldKey,
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
//automaticallyImplyLeading: false, // убрать кнопку "назад"
title: Text('Добавление автомобиля', style: TextStyle(fontFamily: 'Poppins', color: Colors.black, fontSize: 22,),),
actions: [],
centerTitle: false,
elevation: 2,
),
body:
SingleChildScrollView(
child:
Column(
children: [
Visibility(
visible: auto_list_is_loading,
child:
Container(
padding: const EdgeInsets.all(8),
child:
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
itemCount: FilteredListAutoes.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
print("тыкнули по модели автомобиля..");
cur_model_select=index;
});
},
child:
Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 16,
width: 16,
child:
Transform.rotate(
angle: index==cur_model_select?90 * 3.14/180:0,
child:
IconButton (
padding: const EdgeInsets.all(0),
icon: SvgPicture.asset('lib/images/arrow-right-small.svg',height: 16,width: 16,),
onPressed: () {print('IconButton pressed ...');},
),
)
),
Text(
FilteredListAutoes[index]["name"]+" ("+FilteredListAutoes[index]["cars"].length.toString()+")",
style: TextStyle(
fontWeight: index==cur_model_select?FontWeight.bold:FontWeight.w100,
)
)
],
),
// рисуем список автомобилей
AnimatedOpacity(
opacity: index==cur_model_select ? 1.0 : 0.0,
duration: const Duration(milliseconds: 1500),
child:
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
itemCount: FilteredListAutoes[index]["cars"].length,
itemBuilder: (BuildContext context, int index2) {
return
Visibility(
visible: index==cur_model_select?true:false,
child:
Padding(
padding: const EdgeInsets.only(left: 20,top: 4),
child:
GestureDetector(
onTap: () {
print("тыкнули по автомобилю.."+index2.toString());
},
child: Text(FilteredListAutoes[index]["cars"][index2]["name"]),
)
)
);
}
),
),
new Divider()
]
)
);
}
),
)
),
Visibility(
visible: !auto_list_is_loading,
child:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
CardLoading(
height: 30,
borderRadius: BorderRadius.all(Radius.circular(15)),
width: 100,
margin: EdgeInsets.only(bottom: 10),
),
CardLoading(
height: 100,
borderRadius: BorderRadius.all(Radius.circular(15)),
margin: EdgeInsets.only(bottom: 10),
),
]
)
),
],
)
)
);
}
}