Commit 07510a90 authored by Aleksi Suomalainen's avatar Aleksi Suomalainen Committed by GitHub

Merge pull request #52 from neochapay/datepicker

Datepicker
parents 7d581654 b459ff95
/****************************************************************************************
**
** Copyright (C) 2017 Chupligin Sergey <neochapay@gmail.com>
** All rights reserved.
**
** You may use this file under the terms of BSD license as follows:
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the author nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
****************************************************************************************/
import QtQuick 2.6
import QtQuick.Controls 1.0 //needed for the Stack attached property
import QtQuick.Controls.Nemo 1.0
import QtQuick.Controls.Styles.Nemo 1.0
Page {
id: root
headerTools: HeaderToolsLayout { showBackButton: true; title: "Date Picker" }
Column {
spacing: 40
width: parent.width
DatePicker{
}
}
}
...@@ -80,6 +80,10 @@ ApplicationWindow { ...@@ -80,6 +80,10 @@ ApplicationWindow {
title: "ProgressBar" title: "ProgressBar"
page: "content/ProgressBarPage.qml" page: "content/ProgressBarPage.qml"
} }
ListElement {
title: "DatePicker"
page: "content/DatePickerPage.qml"
}
ListElement { ListElement {
title: "Tabs" title: "Tabs"
page: "content/TabBarPage.qml" page: "content/TabBarPage.qml"
......
...@@ -25,7 +25,8 @@ qml.files += \ ...@@ -25,7 +25,8 @@ qml.files += \
content/QueryDialogPage.qml \ content/QueryDialogPage.qml \
content/ListViewPage.qml \ content/ListViewPage.qml \
content/SelectRollerPage.qml \ content/SelectRollerPage.qml \
content/IconPage.qml content/IconPage.qml \
content/DatePickerPage.qml
qml.path = /usr/share/glacier-components/content qml.path = /usr/share/glacier-components/content
......
...@@ -63,6 +63,7 @@ desktop-file-install --delete-original \ ...@@ -63,6 +63,7 @@ desktop-file-install --delete-original \
%files %files
%defattr(-,root,root,-) %defattr(-,root,root,-)
%{_libdir}/qt5/qml/org/nemomobile/models/
%{_libdir}/qt5/qml/QtQuick/Controls/Nemo %{_libdir}/qt5/qml/QtQuick/Controls/Nemo
%{_libdir}/qt5/qml/QtQuick/Controls/Styles/Nemo %{_libdir}/qt5/qml/QtQuick/Controls/Styles/Nemo
......
/****************************************************************************************
**
** Copyright (C) 2017 Chupligin Sergey <neochapay@gmail.com>
** All rights reserved.
**
** You may use this file under the terms of BSD license as follows:
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the author nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
****************************************************************************************/
import QtQuick 2.6
import QtQuick.Controls.Nemo 1.0
import Nemo.Models 1.0
Item {
id: datePicker
width: parent.width
height: childrenRect.height
property date currentDate: new Date()
property var monthNames: [qsTr("January"), qsTr("February"), qsTr("March"), qsTr("April"), qsTr("May"), qsTr("June"),qsTr("July"), qsTr("August"), qsTr("September"), qsTr("October"), qsTr("November"), qsTr("December")];
signal monthChanged()
signal dateSelect(var date)
Item {
id: header
width: parent.width
height: Theme.itemHeightLarge
anchors{
left: parent.left
right: parent.right
top: parent.top
}
Image{
id: leftArrow
width: Theme.itemHeightMedium
height: width
anchors{
left: parent.left
leftMargin: (Theme.itemHeightLarge-Theme.itemHeightMedium)/2
top: parent.top
topMargin: (Theme.itemHeightLarge-Theme.itemHeightMedium)/2
}
source: "image://theme/caret-left"
MouseArea{
anchors.fill: parent
onClicked: {
var newDate = currentDate;
if(newDate.getMonth() == 1)
{
newDate.setFullYear(currentDate.getFullYear()-1)
newDate.setMonth(12)
}
else
{
newDate.setMonth(currentDate.getMonth()-1)
}
datePicker.currentDate = newDate
monthChanged()
}
}
}
Label{
id: monthLabel
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeLarge
color: Theme.textColor
text: monthNames[currentDate.getMonth()]
}
Image{
id: rightArrow
width: Theme.itemHeightMedium
height: width
anchors{
right: parent.right
rightMargin: (Theme.itemHeightLarge-Theme.itemHeightMedium)/2
top: parent.top
topMargin: (Theme.itemHeightLarge-Theme.itemHeightMedium)/2
}
source: "image://theme/caret-right"
MouseArea{
anchors.fill: parent
onClicked: {
var newDate = currentDate;
if(newDate.getMonth() == 12)
{
newDate.setFullYear(currentDate.getFullYear()+1)
newDate.setMonth(1)
}
else
{
newDate.setMonth(currentDate.getMonth()+1)
}
datePicker.currentDate = newDate
monthChanged()
}
}
}
}
Item{
id: weekDays
width: parent.width
height: width/7
anchors{
top: header.bottom
topMargin: Theme.itemSpacingSmall
}
ListModel{
id: weekendModel
ListElement{
label: qsTr("Mon")
isWeekEnd: false
}
ListElement{
label: qsTr("Tue")
isWeekEnd: false
}
ListElement{
label: qsTr("Wed")
isWeekEnd: false
}
ListElement{
label: qsTr("Thu")
isWeekEnd: false
}
ListElement{
label: qsTr("Fri")
isWeekEnd: false
}
ListElement{
label: qsTr("Sat")
isWeekEnd: true
}
ListElement{
label: qsTr("Sun")
isWeekEnd: true
}
}
ListView{
id: weekendListView
width: parent.width
height: parent.height
model: weekendModel
orientation: ListView.Horizontal
delegate: Item{
height: parent.height
width: height
Label{
text: label
anchors.centerIn: parent
color: isWeekEnd ? Theme.accentColor : Theme.textColor
font.pixelSize: (parent.height*0.45 < Theme.fontSizeLarge) ? parent.height*0.45 : Theme.fontSizeLarge
}
}
}
}
GridView {
id: daysGrid
width: parent.width
height: width
anchors {
top: weekDays.bottom
topMargin: Theme.itemSpacingSmall
}
cellWidth: width / 7 - 1
cellHeight: width / 6
model: calendarModel
delegate: Item{
id: dayCell
width: parent.width/7
height: parent.height/6
property bool isOtherMonthDay: false
property bool isCurrentDay: false
property bool isSelectedDay: false
property bool hasEventDay: false
property date dateOfDay
function setColor(model)
{
var color = Theme.textColor;
/*If weekend*/
if(model.dateOfDay.getDay() === 0 || model.dateOfDay.getDay() === 6)
{
if(model.isCurrentDay)
{
color = Theme.textColor
}
else
{
color = Theme.accentColor;
}
}
if(model.isOtherMonthDay)
{
color = Theme.fillDarkColor
}
return color;
}
Rectangle{
width: parent.width
height: parent.height
color: Theme.accentColor
visible: model.isCurrentDay
}
Label{
text: model.dateOfDay.getDate()
anchors.centerIn: parent
color: setColor(model)
font.pixelSize: (parent.height*0.45 < Theme.fontSizeLarge) ? parent.height*0.45 : Theme.fontSizeLarge
}
MouseArea{
anchors.fill: parent
onClicked: {
datePicker.dateSelect(model.dateOfDay)
}
}
}
}
onMonthChanged: {
calendarModel.selectedDate = datePicker.currentDate
calendarModel.month = currentDate.getMonth()+1;
calendarModel.year = currentDate.getFullYear();
}
CalendarModel{
id: calendarModel
}
}
...@@ -23,7 +23,8 @@ QML_FILES += \ ...@@ -23,7 +23,8 @@ QML_FILES += \
GlacierRoller.qml \ GlacierRoller.qml \
GlacierRollerItem.qml \ GlacierRollerItem.qml \
InverseMouseArea.qml \ InverseMouseArea.qml \
IconButton.qml IconButton.qml \
DatePicker.qml
OTHER_FILES += qmldir \ OTHER_FILES += qmldir \
$$QML_FILES $$QML_FILES
......
...@@ -25,6 +25,7 @@ GlacierRoller 1.0 GlacierRoller.qml ...@@ -25,6 +25,7 @@ GlacierRoller 1.0 GlacierRoller.qml
GlacierRollerItem 1.0 GlacierRollerItem.qml GlacierRollerItem 1.0 GlacierRollerItem.qml
InverseMouseArea 1.0 InverseMouseArea.qml InverseMouseArea 1.0 InverseMouseArea.qml
IconButton 1.0 IconButton.qml IconButton 1.0 IconButton.qml
DatePicker 1.0 DatePicker.qml
# MIRRORED CONTROLS: # MIRRORED CONTROLS:
# These are the controls that we take directly from official QQC. # These are the controls that we take directly from official QQC.
......
/****************************************************************************************
**
** Copyright (C) 2017 Chupligin Sergey <neochapay@gmail.com>
** All rights reserved.
**
** You may use this file under the terms of BSD license as follows:
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the author nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
****************************************************************************************/
#include "calendarmodel.h"
CalendarModel::CalendarModel(QObject *parent) :
QAbstractListModel(parent)
{
m_hash.insert(Qt::UserRole ,QByteArray("isOtherMonthDay"));
m_hash.insert(Qt::UserRole+1 ,QByteArray("isCurrentDay"));
m_hash.insert(Qt::UserRole+2 ,QByteArray("isSelectedDay"));
m_hash.insert(Qt::UserRole+3 ,QByteArray("hasEventDay"));
m_hash.insert(Qt::UserRole+4 ,QByteArray("dateOfDay"));
m_currentDate = QDate::currentDate();
m_year = m_currentDate.year();
m_month = m_currentDate.month();
fill();
}
int CalendarModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_dateList.count();
}
QVariant CalendarModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role);
if(!index.isValid())
{
return QVariant();
}
if(index.row() >= m_dateList.size())
{
return QVariant();
}
DateItem item = m_dateList.at(index.row());
switch (role)
{
case Qt::UserRole:
return item.isOtherMonthDay;
case Qt::UserRole+1:
return item.isCurrentDay;
case Qt::UserRole+2:
return item.isSelectedDay;
case Qt::UserRole+3:
return item.hasEventDay;
case Qt::UserRole+4:
return item.dateOfDay;
default:
return QVariant();
}
}
QVariant CalendarModel::get(const int idx) const
{
if(idx >= m_dateList.size())
{
return QVariant();
}
QMap<QString, QVariant> itemData;
DateItem item = m_dateList.at(idx);
itemData.insert("isOtherMonthDay",item.isOtherMonthDay);
itemData.insert("isCurrentDay",item.isCurrentDay);
itemData.insert("isSelectedDay",item.isSelectedDay);
itemData.insert("hasEventDay",item.hasEventDay);
itemData.insert("dateOfDay",item.dateOfDay);
return QVariant(itemData);
}
void CalendarModel::setSelectedDate(QDate date)
{
if(m_selectedDate != date)
{
m_selectedDate = date;
emit selectedDateChanged();
}
}
void CalendarModel::setMonth(int month)
{
if(m_month != month && month > 0 && month < 13)
{
m_month = month;
fill();
emit monthChanged();
}
}
void CalendarModel::setYear(int year)
{
if(m_year != year)
{
m_year = year;
fill();
emit yearChanged();
}
}
void CalendarModel::fill()
{
m_dateList.clear();
QDate firstDayOfSelectedMonth = QDate(m_year,m_month,1);
int startWeekDay = firstDayOfSelectedMonth.dayOfWeek();
/*If first dayof moth not Monday add form preview month*/
if(startWeekDay != 1)
{
int needToAdd = 1-startWeekDay;
for(int n=needToAdd; n<0; n++)
{
m_dateList.append(createDateItem(firstDayOfSelectedMonth.addDays(n),true));
}
}
for(int n=0; n < firstDayOfSelectedMonth.daysInMonth();n++)
{
QDate date = firstDayOfSelectedMonth.addDays(n);
m_dateList.append(createDateItem(date,false,date == m_currentDate));
}
/*if last day of moth not Sunday add from next mont*/
QDate lastDayOfSelectedMonth = QDate(m_year,m_month,firstDayOfSelectedMonth.daysInMonth());
int endWeekDay = lastDayOfSelectedMonth.dayOfWeek();
if(endWeekDay != 7)
{
int needToAdd = 7-endWeekDay;
for(int n=1;n<=needToAdd;n++)
{
m_dateList.append(createDateItem(lastDayOfSelectedMonth.addDays(n),true));
}
}
dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 0));
}
CalendarModel::DateItem CalendarModel::createDateItem(QDate dateOfDay, bool isOtherMonthDay, bool isCurrentDay, bool isSelectedDay, bool hasEventDay)
{
DateItem dateItem;
dateItem.dateOfDay = dateOfDay;
dateItem.hasEventDay = hasEventDay;
dateItem.isCurrentDay = isCurrentDay;
dateItem.isOtherMonthDay = isOtherMonthDay;
dateItem.isSelectedDay = isSelectedDay;
return dateItem;
}
/****************************************************************************************
**
** Copyright (C) 2017 Chupligin Sergey <neochapay@gmail.com>
** All rights reserved.
**
** You may use this file under the terms of BSD license as follows:
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the author nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
****************************************************************************************/
#ifndef DATELISTMODEL_H
#define DATELISTMODEL_H
#include <QAbstractListModel>
#include <QDate>
class CalendarModel : public QAbstractListModel
{
Q_OBJECT
struct DateItem{
bool isOtherMonthDay;
bool isCurrentDay;
bool isSelectedDay;
bool hasEventDay;
QDate dateOfDay;
};
Q_PROPERTY(QDate currentDate READ currentDate)
Q_PROPERTY(int month READ month WRITE setMonth NOTIFY monthChanged)
Q_PROPERTY(int year READ year WRITE setYear NOTIFY yearChanged)
Q_PROPERTY(QDate selectedDate READ selectedDate WRITE setSelectedDate NOTIFY selectedDateChanged)
public:
explicit CalendarModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const {return m_hash;}
void setSelectedDate(QDate date);
void setMonth(int month);
void setYear(int year);
const QDate currentDate(){return m_currentDate;}
QDate selectedDate(){return m_selectedDate;}
int month(){return m_month;}
int year(){return m_year;}
public slots:
QVariant get(const int idx) const;
signals:
void selectedDateChanged();
void monthChanged();
void yearChanged();
private:
QHash<int,QByteArray> m_hash;
QList<DateItem> m_dateList;
void fill();
DateItem createDateItem(QDate dateOfDay,
bool isOtherMonthDay = false,
bool isCurrentDay = false,
bool isSelectedDay = false,
bool hasEventDay = false);
QDate m_currentDate;
QDate m_selectedDate;
int m_month;
int m_year;
};
#endif // DATELISTMODEL_H
TEMPLATE = lib
TARGET = nemomodels
PLUGIN_IMPORT_PATH = Nemo/Models
QT -= gui
QT += qml
CONFIG += qt plugin hide_symbols
SOURCES += \
calendarmodel.cpp \
plugin.cpp
HEADERS += \
calendarmodel.h
target.path = $$[QT_INSTALL_QML]/$$PLUGIN_IMPORT_PATH
qmlfiles.files =\
qmldir
qmlfiles.path = $$[QT_INSTALL_QML]/$$PLUGIN_IMPORT_PATH
INSTALLS += target qmlfiles
DISTFILES += \
qmldir
/*
* Copyright (C) 2017 Chupligin Sergey <neochapay@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "calendarmodel.h"
#include <QtQml>
#include <QtGlobal>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
class Q_DECL_EXPORT NemoSettingsPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "Nemo.Models")
public:
virtual ~NemoSettingsPlugin() { }
void initializeEngine(QQmlEngine *, const char *uri)
{
Q_ASSERT(uri == QLatin1String("Nemo.Models") || uri == QLatin1String("org.nemomobile.models"));
}
void registerTypes(const char *uri)
{
Q_ASSERT(uri == QLatin1String("Nemo.Models") || uri == QLatin1String("org.nemomobile.models"));
qmlRegisterType<CalendarModel>(uri, 1, 0, "CalendarModel");
}
};
#include "plugin.moc"
module Nemo.Models
plugin nemomodels
...@@ -2,3 +2,4 @@ TEMPLATE = subdirs ...@@ -2,3 +2,4 @@ TEMPLATE = subdirs
SUBDIRS += controls SUBDIRS += controls
SUBDIRS += styles SUBDIRS += styles
SUBDIRS += models
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment