code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef REMOTEMANAGER_H
#define REMOTEMANAGER_H

#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <QtCore/QSharedPointer>
#include <QtCore/QUrl>
#include <QtDeclarative/QDeclarativeListProperty>

#include "qcalevent.h"

class QDeclarativeContext;
class QEventLoop;
class QNetworkAccessManager;
class QNetworkReply;
class SessionModel;
class QUrl;

typedef QList<QObject *> EventList;

Q_DECLARE_METATYPE(QCalEvent*)
Q_DECLARE_METATYPE(QList<QCalEvent*>)
//Q_DECLARE_METATYPE(QList<QList<QCalEvent*> >)

class ParserRunnable :public QObject, public QRunnable
{
    Q_OBJECT
signals:
    void done(QList<QSharedPointer<QCalEvent> > events);
};

class FileParserRunnable : public ParserRunnable
{
public:
    FileParserRunnable(const QString path) : m_path(path) {}
    void run();

private:
    FileParserRunnable() {}
    QString m_path;
};

class NetworkParser : public ParserRunnable
{
    Q_OBJECT
public:
    NetworkParser();

signals:
    void error(const QString &description);

private slots:
    void parseReply(QNetworkReply *reply);

private:
    void run() {}
};

class RemoteManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(SessionModel *sessionModel READ sessionModel NOTIFY sessionModelChanged)
public:
    explicit RemoteManager(QObject *parent = 0);
    ~RemoteManager();

    Q_INVOKABLE void clearCache(const QUrl &url);
    Q_INVOKABLE bool hasCache(const QUrl &url) const;
    static QString cacheFilePath(const QUrl &url);

    SessionModel *sessionModel() const { return m_sessionModel; }
    void setSessionModel(SessionModel *model) { m_sessionModel = model; emit sessionModelChanged(); }

signals:
    void sessionModelChanged();
    void eventsChanged();

    /**
     * This signal is emitted when the RemoteManager encountered and error.
     * When the signal is received the RemoteManager is in a state where error
     * handling can be done immediately (i.e. the signal is emitted only once
     * the function that caused the error returns to QEventLoop.
     *
     * \param description a human readable description of the error
     */
    void error(const QString &description);

public slots:
    void update(const QUrl &url);
    void updateFromCache(const QUrl &url);

private slots:
    void parsingDone(QList<QSharedPointer<QCalEvent> > events);

private:
    QList<EventList> m_list;

    SessionModel *m_sessionModel;
};

#endif // REMOTEMANAGER_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "SessionModel.h"

#include <QtCore/QDebug>

SessionModel::SessionModel(QObject *parent) :
    QAbstractTableModel(parent)
{
    QHash<int, QByteArray> roles;
    roles[Qt::DisplayRole] = "display";
    roles[UidRole] = "uid";
    roles[StartDateTimeRole] = "startDateTime";
    roles[EndDateTimeRole] = "endDateTime";
    roles[CategoriesRole] = "categories";
    roles[SummaryRole] = "summary";
    roles[LocationRole] = "location";
    roles[DescriptionRole] = "description";
    roles[UrlRole] = "url";
    roles[TrackRole] = "track";
    roles[RoomNameRole] = "room";
    setRoleNames(roles);
//    m_columns = roles.count() + 1;
}

void SessionModel::setEvents(const EventList &events)
{
    m_events = events;
}

int SessionModel::rowCount(const QModelIndex &parent) const
{
    Q_ASSERT(!parent.isValid());
    return m_events.count();
}

int SessionModel::columnCount(const QModelIndex &parent) const
{
    Q_ASSERT(!parent.isValid());
    return 1;
}

QVariant SessionModel::data(const QModelIndex &index, int role) const
{
    QSharedPointer<QCalEvent> event = m_events.at(index.row());

    switch (static_cast<Qt::ItemDataRole>(role)) {
    case Qt::DisplayRole:
        return event->summary();
        break;
    }

    switch (static_cast<EventRole>(role)) {
    case UidRole:
        return event->summary();
    case StartDateTimeRole:
        return event->dtstart();
    case EndDateTimeRole:
        return event->dtend();
    case CategoriesRole:
        return event->categories();
    case SummaryRole:
        return event->summary();
    case LocationRole:
        return event->location();
    case DescriptionRole:
        return event->description();
    case UrlRole:
        return event->url();
    case TrackRole:
        return event->property("x_type");
    case RoomNameRole:
        return event->property("x_roomname");
    }
    qDebug() << Q_FUNC_INFO << "unhandled role" << role << "for index" << index;

    return QVariant();
}

repo_name: snapshot/uds

license: mit


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef SESSIONMODEL_H
#define SESSIONMODEL_H

#include <QtCore/QAbstractTableModel>
#include <QtCore/QDateTime>
#include <QtCore/QSharedPointer>
#include <QtGui/QSortFilterProxyModel>

#include "qcalevent.h"

class SessionModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    typedef QList<QSharedPointer<QCalEvent> > EventList;

    enum EventRole {
        UidRole = Qt::UserRole + 1,
        StartDateTimeRole,
        EndDateTimeRole,
        CategoriesRole,
        SummaryRole,
        LocationRole,
        DescriptionRole,
        UrlRole,
        TrackRole,
        RoomNameRole,
        LastEventRole = RoomNameRole
    };

    enum DayRole {
        MondayRole = LastEventRole + 1,
        TuesdayRole,
        WednesdayRole,
        ThursdayRole,
        FridayRole,
        SaturdayRole,
        SundayRole
    };

    explicit SessionModel(QObject *parent = 0);
    void setEvents(const EventList &events);

    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;

    QVariant data(const QModelIndex &index, int role) const;

    // Provided by QAbstractTableModel
//    QModelIndex index(int row, int column, const QModelIndex &parent) const;
//    QModelIndex parent(const QModelIndex &child) const;

// TODO
//    QVariant headerData(int section, Qt::Orientation orientation, int role) const;

private:
    EventList m_events;
};

class FilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT
    Q_PROPERTY(int dateFilter READ dateFilter WRITE setDateFilter)
    Q_PROPERTY(SessionModel *sourceModel READ sourceModel WRITE setSourceModel)
public:
    FilterProxyModel(QObject *parent = 0) : QSortFilterProxyModel(parent), m_sourceModel(0), m_dateFilter(-1) {}

    SessionModel *sourceModel() const { return m_sourceModel; }
    void setSourceModel(SessionModel *sourceModel)
    {
        beginResetModel();
        m_sourceModel = sourceModel;
        QSortFilterProxyModel::setSourceModel(sourceModel);
        endResetModel();
    }

    int dateFilter() const { return m_dateFilter; }
    void setDateFilter(int filter) { m_dateFilter = filter; }

    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
    {
        QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

        if (m_dateFilter > -1)
            return index.data(SessionModel::StartDateTimeRole).value<QDateTime>().date().dayOfWeek() == m_dateFilter;

        return true;
    }

private:
    SessionModel *m_sourceModel;
    int m_dateFilter;
};

#endif // SESSIONMODEL_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef SETTINGS_H
#define SETTINGS_H

#include <QtCore/QSettings>
#include <QtCore/QStringList>

class Settings : public QSettings
{
    Q_OBJECT
    Q_PROPERTY(QStringList allKeys READ allKeys)
    Q_PROPERTY(QString group READ group NOTIFY groupChanged)
public:
    Settings(QObject *parent = 0) : QSettings(parent) {}
    ~Settings() {}

    // Properties
    QStringList allKeys() const { return QSettings::allKeys(); }
    QString group() const { return QSettings::group(); }

    // Invokables
    Q_INVOKABLE void beginGroup(const QString &prefix) { QSettings::beginGroup(prefix); emit groupChanged(); }
    Q_INVOKABLE void endGroup() { QSettings::endGroup(); emit groupChanged(); }

    Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const
    {
        return QSettings::value(key, defaultValue);
    }

signals:
    void groupChanged();

public slots:
    void setValue(const QString &key, const QVariant &value)
    {
        QSettings::setValue(key, value);
    }

    void sync() { QSettings::sync(); }
};

#endif // SETTINGS_H


code:
#include "AndroidQmlAppViewer.h"

#include <QtCore/QDebug>
#include <QtGui/QCloseEvent>

AndroidQmlAppViewer::AndroidQmlAppViewer(QWidget *parent) :
    QmlApplicationViewer(parent),
    m_closePressed(false)
{
}

void AndroidQmlAppViewer::closeEvent(QCloseEvent *event)
{
    qDebug() << Q_FUNC_INFO << event->spontaneous();

    // Spontanous (i.e. originated from outside the system) close requests are
    // ignored if the back key was pressed.
    // This is to work around a major misdesign in QtAndroid where it will trigger
    // a close event on every press on back, which is of course only desirable
    // if our QML PageStack is empty. So we simply ignore all spontanous events
    // that seem to be caused by this bogus behaviour. Other spontanous events
    // are processed as expected (system asking us to quit to get memory etc.).
    if (event->spontaneous() && m_closePressed) {
        event->setAccepted(false);
        m_closePressed = false;
        return;
    }

    QmlApplicationViewer::closeEvent(event);
}

void AndroidQmlAppViewer::keyPressEvent(QKeyEvent *event)
{
    qDebug() << Q_FUNC_INFO;
    if (event->key() == Qt::Key_Close)
        m_closePressed = true;
    QmlApplicationViewer::keyPressEvent(event);
}


code:
#ifndef ANDROIDQMLAPPVIEWER_H
#define ANDROIDQMLAPPVIEWER_H

#include "../qmlapplicationviewer/qmlapplicationviewer.h"

class AndroidQmlAppViewer : public QmlApplicationViewer
{
    Q_OBJECT
public:
    explicit AndroidQmlAppViewer(QWidget *parent = 0);

protected:
    void closeEvent(QCloseEvent *);
    void keyPressEvent(QKeyEvent *);

private:
    bool m_closePressed;
};

#endif // ANDROIDQMLAPPVIEWER_H


code:
#ifndef PAGESTATUS_H
#define PAGESTATUS_H

#include <QtCore/QObject>

class PageStatus : public QObject
{
    Q_OBJECT
    Q_ENUMS(Status)
public:
    enum Status {
        Inactive,
        Activating,
        Active,
        Deactivating
    };
};

#endif // PAGESTATUS_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "DeclarativeShell.h"

#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtDeclarative/QDeclarativeComponent>
#include <QtDeclarative/QDeclarativeEngine>
#include <QtGui/QGraphicsObject>

DeclarativeShell::DeclarativeShell(QObject *parent) :
    QObject(parent),
    m_component(0),
    m_engine(new QDeclarativeEngine(this))
{
    connect(m_engine, SIGNAL(quit()), qApp, SLOT(quit()));
}

DeclarativeShell::~DeclarativeShell()
{
}

static QString adjustPath(const QString &path)
{
#ifdef Q_OS_UNIX
#ifdef Q_OS_MAC
    if (!QDir::isAbsolutePath(path))
        return QCoreApplication::applicationDirPath()
                + QLatin1String("/../Resources/") + path;
#else
    const QString pathInInstallDir = QCoreApplication::applicationDirPath()
        + QLatin1String("/../") + path;
    if (pathInInstallDir.contains(QLatin1String("opt"))
            && pathInInstallDir.contains(QLatin1String("bin"))
            && QFileInfo(pathInInstallDir).exists()) {
        return pathInInstallDir;
    }
#endif
#endif
    return path;
}

void DeclarativeShell::setMainQmlFile(const QString &file)
{
    QUrl url = QUrl::fromLocalFile(adjustPath(file));
    // FIXME this is all a bit flimsy
    m_component = new QDeclarativeComponent(m_engine, url, this);
    m_component->create(m_engine->rootContext());
}

inline QDeclarativeEngine *DeclarativeShell::engine() const
{
    return m_engine;
}

QDeclarativeContext *DeclarativeShell::rootContext() const
{
    return m_engine->rootContext();
}


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef DECLARATIVESHELL_H
#define DECLARATIVESHELL_H

#include <QtCore/QObject>

class QDeclarativeComponent;
class QDeclarativeContext;
class QDeclarativeEngine;

class DeclarativeShell : public QObject
{
    Q_OBJECT
public:
    DeclarativeShell(QObject *parent = 0);
    ~DeclarativeShell();

    void setMainQmlFile(const QString &file);

    QDeclarativeEngine *engine() const;
    QDeclarativeContext *rootContext() const;

    // Discard, needed for compatiblity with qmlappviewer
    void showExpanded() {}

private:
    QDeclarativeComponent *m_component;
    QDeclarativeEngine *m_engine;
};

#endif // DECLARATIVESHELL_H


code:
#include "MenuGroup.h"

#include <QtCore/QDebug>
#include <QtGui/QActionGroup>

#include "MenuItem.h"

MenuGroup::MenuGroup(QDeclarativeItem *parent) :
    QDeclarativeItem(parent),
    m_actionGroup(new QActionGroup(this))
{
    qDebug() << Q_FUNC_INFO;
    m_actionGroup->setExclusive(true);
}

void MenuGroup::componentComplete()
{
    qDebug() << Q_FUNC_INFO;
    MenuItem *item = 0;
    foreach (QObject *child, children()) {
        item = qobject_cast<MenuItem *>(child);
        if (item) {
            item->setActionGroup(m_actionGroup);
            item->setCheckable(true);
        }
    }
}


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MENUGROUP_H
#define MENUGROUP_H

#include <QtDeclarative/QDeclarativeItem>

class QActionGroup;
class MenuGroup : public QDeclarativeItem
{
    Q_OBJECT
public:
    explicit MenuGroup(QDeclarativeItem *parent = 0);

    void componentComplete();

    QList<QAction *> actions() const { return m_actionGroup->actions(); }

private:
    QActionGroup *m_actionGroup;
};

#endif // MENUGROUP_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "MenuItem.h"

#include <QtCore/QDebug>
#include <QtGui/QAction>

MenuItem::MenuItem(QObject *parent) :
    QAction(parent)
{
    qDebug() << Q_FUNC_INFO;
//    if (qobject_cast<QActionGroup *>(parent))
//        setCheckable(true);
}


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MENUITEM_H
#define MENUITEM_H

#include <QtGui/QAction>

class MenuItem : public QAction
{
    Q_OBJECT
public:
    explicit MenuItem(QObject *parent = 0);
};

#endif // MENUITEM_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Window.h"

#include <QtGui/QApplication>
#include <QtGui/QDesktopWidget>
#include <QtGui/QResizeEvent>

void WindowStack::push(QObject *object)
{
    Window *window = qobject_cast<Window *>(object);
    Q_ASSERT(window);
    MainWindow *mainWindow = window->mainWindow();
    Q_ASSERT(mainWindow);

    window->setWindowStack(this);

    // The first window on the stack does not need special treatment as it
    // has no QWidget parent, and thus also needs no flag tempering.
    if (!m_stack.isEmpty()) {
        mainWindow->setParent(m_stack.last());
        mainWindow->setWindowFlags(mainWindow->windowFlags() | Qt::Window);
    }
    m_stack.push(mainWindow);
    mainWindow->installEventFilter(this);
    mainWindow->scene()->addItem(window);
    mainWindow->show();
}

bool WindowStack::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::Hide) {
        Q_ASSERT(obj == m_stack.last());
        obj->removeEventFilter(this);
        m_stack.pop();
    }
    // standard event processing
    return QObject::eventFilter(obj, event);
}

// -------------------------------------------------------------------------- //

void StackedWindow::setInitialWindow(Window *window)
{
    m_initialWindow = window;
    if (m_stack->isEmpty())
        m_stack->push(window);
}

// -------------------------------------------------------------------------- //

#include <QtGui/QMenuBar>

MainWindow::MainWindow() :
    m_view(new QDeclarativeView)
{
    setAttribute(Qt::WA_Maemo5StackedWindow);
//    setAttribute(Qt::WA_Maemo5AutoOrientation, true);
    setCentralWidget(m_view);
}

MainWindow::~MainWindow()
{
    delete m_view;
}

bool MainWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::Hide: {
        qDebug() << Q_FUNC_INFO << "DELETE";
        deleteLater();
    }
    case QEvent::Resize: {
        const QResizeEvent *resize = static_cast<const QResizeEvent *>(event);
        emit sizeChanged(resize->size());
        break;
    }
    default:
        break;
    }
    return QMainWindow::event(event);
}

// -------------------------------------------------------------------------- //

#include "MenuGroup.h"
Window::Window(QDeclarativeItem *parent) :
    QDeclarativeItem(parent),
    m_window(new MainWindow),
    m_stack(0)
{
    connect(m_window, SIGNAL(sizeChanged(QSize)),
            this, SLOT(updateSize(QSize)));
    m_window->view()->setResizeMode(QDeclarativeView::SizeRootObjectToView);
}

Window::~Window()
{
}

void Window::addAction(QObject *actionObject)
{
    qDebug() << "hui";
    QAction *action = qobject_cast<QAction *>(actionObject);
    m_window->menuBar()->addAction(action);
}

void Window::addActions(QObject *actionObjects)
{
    MenuGroup *group = qobject_cast<MenuGroup *>(actionObjects);
    m_window->menuBar()->addActions(group->actions());
}

QString Window::title() const
{
    return m_window->windowTitle();
}

void Window::setTitle(const QString &title)
{
    m_window->setWindowTitle(title);
}

WindowStack *Window::windowStack()
{
    return m_stack;
}

void Window::setWindowStack(WindowStack *stack)
{
    m_stack = stack;
}

void Window::updateSize(QSize newSize)
{
    QDeclarativeItem::setSize(newSize);
}


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef WINDOW_H
#define WINDOW_H

#include <QtCore/QStack>
#include <QtDeclarative/QDeclarativeItem>
#include <QtDeclarative/QDeclarativeView>
#include <QtGui/QMainWindow>

class MainWindow;
class Window;
class WindowStack : public QObject
{
    Q_OBJECT
public:
    WindowStack() {}
    ~WindowStack() {}

    bool isEmpty() const { return m_stack.isEmpty(); }

public slots:
    void push(QObject *object);

protected:
    bool eventFilter(QObject *obj, QEvent *event);

private:
    QStack<MainWindow *> m_stack;
};

class Window;
class StackedWindow : public QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(WindowStack* windowStack READ windowStack)
    Q_PROPERTY(Window* initialWindow READ initialWindow WRITE setInitialWindow)
public:
    StackedWindow(QDeclarativeItem *parent = 0) :
        QDeclarativeItem(parent),
        m_initialWindow(0),
        m_stack(new WindowStack)
    {}
    ~StackedWindow()
    {
        delete m_stack;
    }

    WindowStack *windowStack() const { return m_stack; }
    void setWindowStack(WindowStack *stack) { m_stack = stack; }

    Window *initialWindow() const { return m_initialWindow; }
    void setInitialWindow(Window *window);

private:
    Window *m_initialWindow;
    WindowStack *m_stack;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow();
    ~MainWindow();

    QGraphicsScene *scene() { return m_view->scene(); }
    QDeclarativeView *view() { return m_view; }

signals:
    void sizeChanged(QSize newSize);

protected:
    virtual bool event(QEvent *event);

private:
    QDeclarativeView *m_view;
};

class Window : public QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(QFont font READ font)
    Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
    Q_PROPERTY(WindowStack *windowStack READ windowStack)
public:
    Window(QDeclarativeItem *parent = 0);
    ~Window();

    Q_INVOKABLE void addAction(QObject *actionObject);
    Q_INVOKABLE void addActions(QObject *menuGroup);

    QFont font() const { return m_window->font(); }

    QString title() const;
    void setTitle(const QString &title);

    // The stack this window is in, or null
    WindowStack *windowStack();
    void setWindowStack(WindowStack *stack);

    MainWindow *mainWindow() const { return m_window; }

signals:
    void titleChanged();

private slots:
    void updateSize(QSize newSize);

private:
    MainWindow *m_window;
    WindowStack *m_stack;
};

#endif // WINDOW_H


code:
/*
    Copyright (C) 2011 Harald Sitter <apachelogger@ubuntu.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QtCore/QStringBuilder>
#include <QtCore/QThread>

#include <QtDeclarative/QDeclarativeContext>
#include <QtDeclarative/QDeclarativeEngine>
#include <QtDeclarative/QDeclarativeView>
#include <QtDeclarative/qdeclarative.h>

#include <QtGui/QApplication>

#include "qcalevent.h"

#ifdef Q_WS_HARMATTAN
#include <applauncherd/MDeclarativeCache>
#elif Q_WS_MAEMO_5
#include "maemo5/DeclarativeShell.h"
#include "maemo5/MenuGroup.h"
#include "maemo5/MenuItem.h"
#include "maemo5/Window.h"
#elif Q_WS_ANDROID
#include "android/AndroidQmlAppViewer.h"
#include "android/PageStatus.h"
#else
#include "qmlapplicationviewer.h"
#endif

#include "DesktopServices.h"
#include "RemoteManager.h"
#include "SessionModel.h"
#include "Settings.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
#ifdef Q_WS_HARMATTAN
    // Cannot use the qmlapplicationviewer overload for create() as that will
    // lead to view's rootcontext not being the actual execution context which
    // in turn breaks the manager injection (then again the injection should
    // be replaced with qml element usage as the managers are qobjects anyway).
    QScopedPointer<QApplication> app(MDeclarativeCache::qApplication(argc, argv));
    QScopedPointer<QDeclarativeView> view(MDeclarativeCache::qDeclarativeView());
    QObject::connect(view->engine(), SIGNAL(quit()), view.data(), SLOT(close()));
#elif Q_WS_MAEMO_5
    QScopedPointer<QApplication> app(new QApplication(argc, argv));
    QScopedPointer<DeclarativeShell> view(new DeclarativeShell());
#warning TODO
//    view->setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
#elif Q_WS_ANDROID
    QScopedPointer<QApplication> app(new QApplication(argc, argv));
    QScopedPointer<QmlApplicationViewer> view(new AndroidQmlAppViewer());
#else
    QScopedPointer<QApplication> app(new QApplication(argc, argv));
    QScopedPointer<QmlApplicationViewer> view(new QmlApplicationViewer());
    view->setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
#endif

    // For QSettings
    QCoreApplication::setOrganizationName("Ubuntu");
    QCoreApplication::setOrganizationDomain("ubuntu.com");
    QCoreApplication::setApplicationName("summit");

    qmlRegisterType<QCalEvent>();
    qmlRegisterType<RemoteManager>();
    qmlRegisterType<SessionModel>();

    const char uri[] = "com.ubuntu.summit";
    qmlRegisterType<DesktopServices>(uri, 1, 0, "DesktopServices");
    qmlRegisterType<FilterProxyModel>(uri, 1, 0, "FilterProxyModel");
    qmlRegisterType<Settings>(uri, 1, 0, "Settings");
#ifdef Q_WS_MAEMO_5
    const char maemoUri[] = "com.ubuntu.summit.maemo";
    qmlRegisterType<StackedWindow>(maemoUri, 5, 0, "StackedWindow");
    qmlRegisterType<MenuGroup>(maemoUri, 5, 0, "MenuGroup");
    qmlRegisterType<MenuItem>(maemoUri, 5, 0, "MenuItem");
    qmlRegisterType<Window>(maemoUri, 5, 0, "WindowBase");
    qmlRegisterType<WindowStack>();
#elif Q_WS_ANDROID
    const char androidUri[] = "com.ubuntu.summit.android";
    qmlRegisterUncreatableType<PageStatus>(androidUri, 1, 0, "PageStatus", "");
#endif

    QDeclarativeContext *rootContext = view->rootContext();
    RemoteManager mainCalendar;
    RemoteManager userCalendar;
    rootContext->setContextProperty("mainCalendar", &mainCalendar);
    rootContext->setContextProperty("userCalendar", &userCalendar);

    QString source =
        #ifdef Q_WS_HARMATTAN
            MDeclarativeCache::applicationDirPath() % QLatin1Literal("/../qml/harmattan/main.qml")
        #elif Q_WS_MAEMO_5
            QLatin1String("qml/maemo5/main.qml")
        #elif Q_WS_SYMBIAN
            QLatin1String("qml/symbian/main.qml")
        #elif Q_WS_ANDROID
            QLatin1String("qrc:/qml/android/main.qml")
        #else
            QLatin1String("qml/desktop/main.qml")
        #endif
            ;

#ifdef Q_WS_HARMATTAN
    view->setSource(source);
    view->showFullScreen();
#else
    view->setMainQmlFile(source);
    view->showExpanded();
#endif

    return app->exec();
}


code:
// checksum 0x78c version 0x60010
/*
  This file was generated by the Qt Quick Application wizard of Qt Creator.
  QmlApplicationViewer is a convenience class containing mobile device specific
  code such as screen orientation handling. Also QML paths and debugging are
  handled here.
  It is recommended not to modify this file, since newer versions of Qt Creator
  may offer an updated version of it.
*/

#include "qmlapplicationviewer.h"

#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtDeclarative/QDeclarativeComponent>
#include <QtDeclarative/QDeclarativeEngine>
#include <QtDeclarative/QDeclarativeContext>
#include <QtGui/QApplication>

#include <qplatformdefs.h> // MEEGO_EDITION_HARMATTAN

#ifdef HARMATTAN_BOOSTER
#include <MDeclarativeCache>
#endif

#if defined(QMLJSDEBUGGER) && QT_VERSION < 0x040800

#include <qt_private/qdeclarativedebughelper_p.h>

#if !defined(NO_JSDEBUGGER)
#include <jsdebuggeragent.h>
#endif
#if !defined(NO_QMLOBSERVER)
#include <qdeclarativeviewobserver.h>
#endif

// Enable debugging before any QDeclarativeEngine is created
struct QmlJsDebuggingEnabler
{
    QmlJsDebuggingEnabler()
    {
        QDeclarativeDebugHelper::enableDebugging();
    }
};

// Execute code in constructor before first QDeclarativeEngine is instantiated
static QmlJsDebuggingEnabler enableDebuggingHelper;

#endif // QMLJSDEBUGGER

class QmlApplicationViewerPrivate
{
    QmlApplicationViewerPrivate(QDeclarativeView *view_) : view(view_) {}

    QString mainQmlFile;
    QDeclarativeView *view;
    friend class QmlApplicationViewer;
    QString adjustPath(const QString &path);
};

QString QmlApplicationViewerPrivate::adjustPath(const QString &path)
{
#ifdef Q_OS_UNIX
#ifdef Q_OS_MAC
    if (!QDir::isAbsolutePath(path))
        return QCoreApplication::applicationDirPath()
                + QLatin1String("/../Resources/") + path;
#else
    QString pathInInstallDir;
    const QString applicationDirPath = QCoreApplication::applicationDirPath();
    pathInInstallDir = QString::fromAscii("%1/../%2").arg(applicationDirPath, path);

    if (QFileInfo(pathInInstallDir).exists())
        return pathInInstallDir;
#endif
#endif
    return path;
}

QmlApplicationViewer::QmlApplicationViewer(QWidget *parent)
    : QDeclarativeView(parent)
    , d(new QmlApplicationViewerPrivate(this))
{
    connect(engine(), SIGNAL(quit()), SLOT(close()));
    setResizeMode(QDeclarativeView::SizeRootObjectToView);
    // Qt versions prior to 4.8.0 don't have QML/JS debugging services built in
#if defined(QMLJSDEBUGGER) && QT_VERSION < 0x040800
#if !defined(NO_JSDEBUGGER)
    new QmlJSDebugger::JSDebuggerAgent(d->view->engine());
#endif
#if !defined(NO_QMLOBSERVER)
    new QmlJSDebugger::QDeclarativeViewObserver(d->view, d->view);
#endif
#endif
}

QmlApplicationViewer::QmlApplicationViewer(QDeclarativeView *view, QWidget *parent)
    : QDeclarativeView(parent)
    , d(new QmlApplicationViewerPrivate(view))
{
    connect(view->engine(), SIGNAL(quit()), view, SLOT(close()));
    view->setResizeMode(QDeclarativeView::SizeRootObjectToView);
    // Qt versions prior to 4.8.0 don't have QML/JS debugging services built in
#if defined(QMLJSDEBUGGER) && QT_VERSION < 0x040800
#if !defined(NO_JSDEBUGGER)
    new QmlJSDebugger::JSDebuggerAgent(d->view->engine());
#endif
#if !defined(NO_QMLOBSERVER)
    new QmlJSDebugger::QDeclarativeViewObserver(d->view, d->view);
#endif
#endif
}

QmlApplicationViewer::~QmlApplicationViewer()
{
    delete d;
}

QmlApplicationViewer *QmlApplicationViewer::create()
{
#ifdef HARMATTAN_BOOSTER
    return new QmlApplicationViewer(MDeclarativeCache::qDeclarativeView(), 0);
#else
    return new QmlApplicationViewer();
#endif
}

void QmlApplicationViewer::setMainQmlFile(const QString &file)
{
    d->mainQmlFile = d->adjustPath(file);
    d->view->setSource(QUrl(d->mainQmlFile));
}

void QmlApplicationViewer::addImportPath(const QString &path)
{
    d->view->engine()->addImportPath(d->adjustPath(path));
}

void QmlApplicationViewer::setOrientation(ScreenOrientation orientation)
{
#if defined(Q_OS_SYMBIAN)
    // If the version of Qt on the device is < 4.7.2, that attribute won't work
    if (orientation != ScreenOrientationAuto) {
        const QStringList v = QString::fromAscii(qVersion()).split(QLatin1Char('.'));
        if (v.count() == 3 && (v.at(0).toInt() << 16 | v.at(1).toInt() << 8 | v.at(2).toInt()) < 0x040702) {
            qWarning("Screen orientation locking only supported with Qt 4.7.2 and above");
            return;
        }
    }
#endif // Q_OS_SYMBIAN

    Qt::WidgetAttribute attribute;
    switch (orientation) {
#if QT_VERSION < 0x040702
    // Qt < 4.7.2 does not yet have the Qt::WA_*Orientation attributes
    case ScreenOrientationLockPortrait:
        attribute = static_cast<Qt::WidgetAttribute>(128);
        break;
    case ScreenOrientationLockLandscape:
        attribute = static_cast<Qt::WidgetAttribute>(129);
        break;
    default:
    case ScreenOrientationAuto:
        attribute = static_cast<Qt::WidgetAttribute>(130);
        break;
#else // QT_VERSION < 0x040702
    case ScreenOrientationLockPortrait:
        attribute = Qt::WA_LockPortraitOrientation;
        break;
    case ScreenOrientationLockLandscape:
        attribute = Qt::WA_LockLandscapeOrientation;
        break;
    default:
    case ScreenOrientationAuto:
        attribute = Qt::WA_AutoOrientation;
        break;
#endif // QT_VERSION < 0x040702
    };
    setAttribute(attribute, true);
}

void QmlApplicationViewer::showExpanded()
{
#if defined(Q_OS_SYMBIAN) || defined(MEEGO_EDITION_HARMATTAN) || defined(Q_WS_SIMULATOR)
    d->view->showFullScreen();
#elif defined(Q_WS_MAEMO_5)
    d->view->showMaximized();
#else
    d->view->show();
#endif
}

QApplication *createApplication(int &argc, char **argv)
{
#ifdef HARMATTAN_BOOSTER
    return MDeclarativeCache::qApplication(argc, argv);
#else
    return new QApplication(argc, argv);
#endif
}


code:
// checksum 0x82ed version 0x60010
/*
  This file was generated by the Qt Quick Application wizard of Qt Creator.
  QmlApplicationViewer is a convenience class containing mobile device specific
  code such as screen orientation handling. Also QML paths and debugging are
  handled here.
  It is recommended not to modify this file, since newer versions of Qt Creator
  may offer an updated version of it.
*/

#ifndef QMLAPPLICATIONVIEWER_H
#define QMLAPPLICATIONVIEWER_H

#include <QtDeclarative/QDeclarativeView>

class QmlApplicationViewer : public QDeclarativeView
{
    Q_OBJECT

public:
    enum ScreenOrientation {
        ScreenOrientationLockPortrait,
        ScreenOrientationLockLandscape,
        ScreenOrientationAuto
    };

    explicit QmlApplicationViewer(QWidget *parent = 0);
    virtual ~QmlApplicationViewer();

    static QmlApplicationViewer *create();

    void setMainQmlFile(const QString &file);
    void addImportPath(const QString &path);

    // Note that this will only have an effect on Symbian and Fremantle.
    void setOrientation(ScreenOrientation orientation);

    void showExpanded();

private:
    explicit QmlApplicationViewer(QDeclarativeView *view, QWidget *parent);
    class QmlApplicationViewerPrivate *d;
};

QApplication *createApplication(int &argc, char **argv);

#endif // QMLAPPLICATIONVIEWER_H


code:
# checksum 0x368d version 0x60010
# This file was generated by the Qt Quick Application wizard of Qt Creator.
# The code below adds the QmlApplicationViewer to the project and handles the
# activation of QML debugging.
# It is recommended not to modify this file, since newer versions of Qt Creator
# may offer an updated version of it.

QT += declarative

SOURCES += $$PWD/qmlapplicationviewer.cpp
HEADERS += $$PWD/qmlapplicationviewer.h
INCLUDEPATH += $$PWD

# Include JS debugger library if QMLJSDEBUGGER_PATH is set
!isEmpty(QMLJSDEBUGGER_PATH) {
    include($$QMLJSDEBUGGER_PATH/qmljsdebugger-lib.pri)
} else {
    DEFINES -= QMLJSDEBUGGER
}

contains(CONFIG,qdeclarative-boostable):contains(MEEGO_EDITION,harmattan) {
    DEFINES += HARMATTAN_BOOSTER
}
# This file was generated by an application wizard of Qt Creator.
# The code below handles deployment to Symbian and Maemo, aswell as copying
# of the application data to shadow build directories on desktop.
# It is recommended not to modify this file, since newer versions of Qt Creator
# may offer an updated version of it.

defineTest(qtcAddDeployment) {
for(deploymentfolder, DEPLOYMENTFOLDERS) {
    item = item$${deploymentfolder}
    itemsources = $${item}.sources
    $$itemsources = $$eval($${deploymentfolder}.source)
    itempath = $${item}.path
    $$itempath= $$eval($${deploymentfolder}.target)
    export($$itemsources)
    export($$itempath)
    DEPLOYMENT += $$item
}

MAINPROFILEPWD = $$PWD

symbian {
    isEmpty(ICON):exists($${TARGET}.svg):ICON = $${TARGET}.svg
    isEmpty(TARGET.EPOCHEAPSIZE):TARGET.EPOCHEAPSIZE = 0x20000 0x2000000
} else:win32 {
    copyCommand =
    for(deploymentfolder, DEPLOYMENTFOLDERS) {
        source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
        source = $$replace(source, /, \\)
        sourcePathSegments = $$split(source, \\)
        target = $$OUT_PWD/$$eval($${deploymentfolder}.target)/$$last(sourcePathSegments)
        target = $$replace(target, /, \\)
        !isEqual(source,$$target) {
            !isEmpty(copyCommand):copyCommand += &&
            isEqual(QMAKE_DIR_SEP, \\) {
                copyCommand += $(COPY_DIR) \"$$source\" \"$$target\"
            } else {
                source = $$replace(source, \\\\, /)
                target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
                target = $$replace(target, \\\\, /)
                copyCommand += test -d \"$$target\" || mkdir -p \"$$target\" && cp -r \"$$source\" \"$$target\"
            }
        }
    }
    !isEmpty(copyCommand) {
        copyCommand = @echo Copying application data... && $$copyCommand
        copydeploymentfolders.commands = $$copyCommand
        first.depends = $(first) copydeploymentfolders
        export(first.depends)
        export(copydeploymentfolders.commands)
        QMAKE_EXTRA_TARGETS += first copydeploymentfolders
    }
} else:unix {
    maemo5 {
        desktopfile.files = $${TARGET}.desktop
        desktopfile.path = /usr/share/applications/hildon
        icon.files = $${TARGET}64.png
        icon.path = /usr/share/icons/hicolor/64x64/apps
    } else:!isEmpty(MEEGO_VERSION_MAJOR) {
        desktopfile.files = $${TARGET}_harmattan.desktop
        desktopfile.path = /usr/share/applications
        icon.files = $${TARGET}80.png
        icon.path = /usr/share/icons/hicolor/80x80/apps
    } else { # Assumed to be a Desktop Unix
        copyCommand =
        for(deploymentfolder, DEPLOYMENTFOLDERS) {
            source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
            source = $$replace(source, \\\\, /)
            macx {
                target = $$OUT_PWD/$${TARGET}.app/Contents/Resources/$$eval($${deploymentfolder}.target)
            } else {
                target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
            }
            target = $$replace(target, \\\\, /)
            sourcePathSegments = $$split(source, /)
            targetFullPath = $$target/$$last(sourcePathSegments)
            !isEqual(source,$$targetFullPath) {
                !isEmpty(copyCommand):copyCommand += &&
                copyCommand += $(MKDIR) \"$$target\"
                copyCommand += && $(COPY_DIR) \"$$source\" \"$$target\"
            }
        }
        !isEmpty(copyCommand) {
            copyCommand = @echo Copying application data... && $$copyCommand
            copydeploymentfolders.commands = $$copyCommand
            first.depends = $(first) copydeploymentfolders
            export(first.depends)
            export(copydeploymentfolders.commands)
            QMAKE_EXTRA_TARGETS += first copydeploymentfolders
        }
    }
    installPrefix = /opt/$${TARGET}
    for(deploymentfolder, DEPLOYMENTFOLDERS) {
        item = item$${deploymentfolder}
        itemfiles = $${item}.files
        $$itemfiles = $$eval($${deploymentfolder}.source)
        itempath = $${item}.path
        $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target)
        export($$itemfiles)
        export($$itempath)
        INSTALLS += $$item
    }

    !isEmpty(desktopfile.path) {
        export(icon.files)
        export(icon.path)
        export(desktopfile.files)
        export(desktopfile.path)
        INSTALLS += icon desktopfile
    }

    target.path = $${installPrefix}/bin
    export(target.path)
    INSTALLS += target
}

export (ICON)
export (INSTALLS)
export (DEPLOYMENT)
export (TARGET.EPOCHEAPSIZE)
export (TARGET.CAPABILITY)
export (LIBS)
export (QMAKE_EXTRA_TARGETS)
}


code:
[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Terminal=false
Name=UDS
Exec=/opt/uds/bin/uds
Icon=uds64
X-Window-Icon=
X-HildonDesk-ShowInToolbar=true
X-Osso-Type=application/x-executable


code:
include(libqcalparser/libqcalparser.pro)
INCLUDEPATH += $$PWD/libqcalparser/src
DEPENDPATH += $$PWD/libqcalparser/src

TEMPLATE = app
TARGET = uds
QT += gui network

# OS components can be forced by adding CONFIG+=os_name to the qmake call.
# Options are: os_harmattan, os_maemo5, os_symbian, os_android, os_desktop.
contains(MEEGO_EDITION, harmattan)|!isEmpty(HARMATTAN) {
    CONFIG += os_harmattan
} else:maemo5 {
    CONFIG += os_maemo5
} else:symbian {
    CONFIG += os_symbian
} else:android {
    CONFIG += os_android
} else {
    CONFIG += os_desktop
}

os_harmattan {                                                       # Harmattan
    message("Building for Harmattan")
    DEFINES += Q_WS_HARMATTAN
    DEFINES += MEEGO_EDITION_HARMATTAN
    CONFIG  += qdeclarative-boostable

    desktopfile.files = $${TARGET}_harmattan.desktop
    desktopfile.path = /usr/share/applications
    INSTALLS += desktopfile

    harmattan_qml.source = qml/harmattan
    harmattan_qml.target = qml
    DEPLOYMENTFOLDERS += harmattan_qml
} else:os_maemo5 {                                                   # Fremantle
    message("Building for Maemo 5")
    DEFINES += Q_WS_MAEMO_5
    SOURCES += \
        src/maemo5/DeclarativeShell.cpp \
        src/maemo5/MenuGroup.cpp \
        src/maemo5/MenuItem.cpp \
        src/maemo5/Window.cpp
    HEADERS += \
        src/maemo5/DeclarativeShell.h \
        src/maemo5/MenuGroup.h \
        src/maemo5/MenuItem.h \
        src/maemo5/Window.h

    maemo_5_qml.source = qml/maemo5
    maemo_5_qml.target = qml
    DEPLOYMENTFOLDERS += maemo_5_qml
} else:os_symbian {                                                    # Symbian
    message("Building for Symbian")
    DEFINES += Q_WS_SYMBIAN

    DEPLOYMENT.display_name = "UDS"
    VERSION = 12.04.0

    symbian:TARGET.UID3 = 0xE054205C

    # Smart Installer package's UID
    # This UID is from the protected range and therefore the package will
    # fail to install if self-signed. By default qmake uses the unprotected
    # range value if unprotected UID is defined for the application and
    # 0x2002CCCF value if protected UID is given to the application
    #symbian:DEPLOYMENT.installer_header = 0x2002CCCF

    # Allow network access on Symbian
    symbian:TARGET.CAPABILITY += NetworkServices

    # Add dependency to symbian components
    CONFIG += qtquickcomponents

    symbian_qml.source = qml/symbian
    symbian_qml.target = qml
    DEPLOYMENTFOLDERS += symbian_qml
} else:os_android {                                                    # Android
    message("Building for Android")
    DEFINES += Q_WS_ANDROID

    SOURCES += \
        src/android/AndroidQmlAppViewer.cpp

    HEADERS += \
        src/android/AndroidQmlAppViewer.h \
        src/android/PageStatus.h

    OTHER_FILES += \
        android/res/drawable-mdpi/icon.png \
        android/res/values/strings.xml \
        android/res/values/libs.xml \
        android/res/drawable-ldpi/icon.png \
        android/res/drawable-hdpi/icon.png \
        android/AndroidManifest.xml \
        android/src/eu/licentia/necessitas/ministro/IMinistro.aidl \
        android/src/eu/licentia/necessitas/ministro/IMinistroCallback.aidl \
        android/src/eu/licentia/necessitas/industrius/QtApplication.java \
        android/src/eu/licentia/necessitas/industrius/QtSurface.java \
        android/src/eu/licentia/necessitas/industrius/QtActivity.java \
        android/src/eu/licentia/necessitas/industrius/QtLayout.java \
        android/src/eu/licentia/necessitas/mobile/QtMediaPlayer.java \
        android/src/eu/licentia/necessitas/mobile/QtLocation.java \
        android/src/eu/licentia/necessitas/mobile/QtFeedback.java \
        android/src/eu/licentia/necessitas/mobile/QtAndroidContacts.java \
        android/src/eu/licentia/necessitas/mobile/QtSensors.java \
        android/src/eu/licentia/necessitas/mobile/QtSystemInfo.java \
        android/src/eu/licentia/necessitas/mobile/QtCamera.java

    RESOURCES += \
        qml/android.qrc

    android_qml.source = qml/android
    android_qml.target = qml
    DEPLOYMENTFOLDERS += android_qml
} else:os_desktop {                                                         # Desktop
    message("Building for Desktop")
    desktop_qml.source = qml/desktop
    desktop_qml.target = qml
    DEPLOYMENTFOLDERS += desktop_qml
} else {
    error("No viable QtQuick target found.")
}

core_qml.source = qml/core
core_qml.target = qml
images_qml.source = qml/images
images_qml.target = qml
DEPLOYMENTFOLDERS += core_qml images_qml

# Additional import path used to resolve QML modules in Creator's code model
QML_IMPORT_PATH =

SOURCES += \
    src/main.cpp \
    src/RemoteManager.cpp \
    src/SessionModel.cpp

HEADERS += \
    src/DesktopServices.h \
    src/RemoteManager.h \
    src/SessionModel.h \
    src/Settings.h

# Qt Creator Deployment Helpers
include(src/qmlapplicationviewer/qmlapplicationviewer.pri)
qtcAddDeployment()

# Icon deployment rules MUST BE after the qtc deployment
os_harmattan {
    icon.files = icons/harmattan/uds80.png
    icon.path = /usr/share/icons/hicolor/80x80/apps
    INSTALLS += icon
} else:os_maemo5{
    icon.files = icons/harmattan/uds64.png
    icon.path = /usr/share/icons/hicolor/64x64/apps
    INSTALLS += icon
} else:os_symbian {
    ICON = icons/symbian/uds.svg
}


code:
[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Terminal=false
Name=UDS
Exec=/usr/bin/invoker --type=d -s /opt/uds/bin/uds
Icon=/usr/share/icons/hicolor/80x80/apps/uds80.png
X-Window-Icon=
X-HildonDesk-ShowInToolbar=true
X-Osso-Type=application/x-executable


code:
# ---> Java
*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# ---> IGORPro
# Avoid including Experiment files: they can be created and edited locally to test the ipf files
*.pxp
*.pxt
*.uxp
*.uxt

# ---> EPiServer
######################
## EPiServer Files
######################
*License.config

repo_name: dnbagency/cach

path: .gitignore


code:
# cach-viet-content-hay

CÁCH VIẾT CONTENT HAY ĐỂ TRỞ THÀNH CÂY VIẾT CHUYÊN NGHIỆP
https://dnbagency.vn/cach-viet-content-hay/
#DNBAgency #dnbagency #giaiphapmarketing #agencybienhoa


code:
# Built application files
/*/build/

# Local configuration file (sdk path, etc)
local.properties

# Gradle generated files
.gradle/
gradle/
gradlew.bat
gradlew

# User-specific configurations
.idea/
*.iml

# OS-specific files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Build output
/app/notabug
/app/play
/app/release

repo_name: tomjschwanke/DSBDirect


code:
# Datenschutzhinweise | Privacy information

## Deutsch | German

Der Entwickler selbst erhält keinerlei Informationen. DSBDirect ist lediglich ein eigenständig entwickeltes Programm, das nicht mit heinekingmedia in Verbindung steht. Diese Dienste werden beim Gebrauch der App angefragt:

### DSB-Server, für DSB-Zugriff

Möglicherweise werden bei den DSB-Endpoints die gleichen Daten wie auf der Webseite digitales-schwarzes-brett.de gespeichert. Dort speichert heinekingmedia manche Daten, die nach einer Woche "gelöscht oder verfremdet" werden. Schau dir lieber [das ganze Ding](https://digitales-schwarzes-brett.de/datenschutz.html) anstatt nur diesen Ausschnitt an:

>Folgende Daten werden erhoben:
>
> 1.  Informationen über den Browsertyp und die verwendete Version
> 2.  Das Betriebssystem des Nutzers
> 3.  Den Internet-Service-Provider des Nutzers
> 4.  Datum und Uhrzeit des Zugriffs
> 5.  Die IP-Adresse des Nutzers (nur anonymisiert)
> 6.  Websites, von denen das System des Nutzers auf unsere Internetseite gelangt

#### Hinweis

Der Entwickler ist bemüht, kann aber nicht versprechen, dass die obigen Informationen aktuell und akkurat sind.

### notabug.org-Server, für Updatebenachrichtigungen

Notabug.org macht keinen Gebrauch von irgendwelchem besonderen Tracking. Zugriffslogs enthalten IP-Adressen, welche nach höchstens zwei Wochen gehasht werden. [Nochmal mit mehr Sätzen lesen.](https://notabug.org/tos#Privacy)

Wenn du keine Updatebenachrichtigungen wünschst, kannst du sie in den Einstellungen ausschalten. In der F-Droid-Ausgabe ist dies bereits standartmäßig der Fall.

Wenn der Server eine ungültige Antwort ausgibt, wird dir angeboten, nach einer Fehlerbehebung zu suchen. Wenn du das tust, wird ganz ähnlich einer Updatesuche eine Verbindung zu notabug.org aufgebaut.

## Englisch | English

The developer does not receive any information by himself. DSBDirect is simply an independent client that is not related to heinekingmedia. These services are contacted during app use:

### DSB servers, for DSB access

Maybe, the same data as on the website digitales-schwarzes-brett.de is collected at the DSB endpoints. On the website, heinekingmedia stores some data, and it is "deleted or alienated" after a week. Excerpt's translation by me. Better check [the full German thing](https://digitales-schwarzes-brett.de/datenschutz.html).

>The following data is collected:
>
> 1.  Information about browser type and used version
> 2.  The user's operating system
> 3.  The user's ISP
> 4.  Access time and date
> 5.  User's IP-Address (anonymized only)
> 6.  Websites from which the user's system reaches our website

#### Notice

The developer is trying to ensure it, but he can't promise that above information is current and accurate.

### notabug.org servers, for update checks

Notabug.org does not employ any special tracking features. Access logs contain IP addresses, which are hashed after at most 2 weeks. [Read this again but with more sentences.](https://notabug.org/tos#Privacy)

If you do not want to use update checks, you can disable them in the preferences. They are already disabled by default in the F-Droid build.

In case the server returns an invalid response, you are able to "look for a fix". Much like checking for updates, this also contacts notabug.org.


code:
# DSBDirect
This Android client software can access a certian **substitution plan** distribution system (aliased **ESB**) which is widely spread in the German-speaking area.

It has been developed independently from heinekingmedia and implements the same functionality as their app.

* Displays entries in a nice, native list which you can filter
* Alternatively displays the raw website or image
* View older versions of your plan, stored locally
* Notifications containing your only entries
* Replace shortcodes with teacher's full names
* View the "News" and "Aushänge" sections
* Loads relatively quickly

## Download

Android 4.0 and up. Notification on Android 5 and up. (Nowadays, nobody has Android 4 anymore. Truly.)

### [Download via release page](https://notabug.org/fynngodau/DSBDirect/releases) | [Download on F-Droid](https://f-droid.org/de/packages/godau.fynn.dsbdirect/) | [Download on Google Play](https://play.google.com/store/apps/details?id=ga.testapp.dsbdirect)

Code mirrors: [notabug.org](https://notabug.org/fynngodau/DSBDirect) | [Codeberg](https://codeberg.org/fynngodau/DSBDirect) | [F-Droid source tarballs](https://f-droid.org/packages/godau.fynn.dsbdirect) | [GitLab](https://gitlab.com/fynngodau/dsbdirectmirror)

## Screenshots

| **Filtered list** ![Filtered list with cards](https://notabug.org/fynngodau/DSBDirect/raw/master/fastlane/metadata/android/de/images/phoneScreenshots/00.png "Filtered list with cards") | **Unfiltered list with dark theme** ![List with dark theme](https://notabug.org/fynngodau/DSBDirect/raw/master/fastlane/metadata/android/de/images/phoneScreenshots/01.png "List with dark theme") |
|:------:|:------:|
| **Website view** ![Website view](https://notabug.org/fynngodau/DSBDirect/raw/master/fastlane/metadata/android/de/images/phoneScreenshots/02.png "Website view") | **Picture view** ![Picture view](https://notabug.org/fynngodau/DSBDirect/raw/master/fastlane/metadata/android/de/images/phoneScreenshots/03.png "Picture view") |

![Boat on water](https://notabug.org/fynngodau/DSBDirect/raw/master/fastlane/metadata/android/en-US/images/featureGraphic.png)


code:
/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

allprojects {
    repositories {
        jcenter()
        google()
        maven { url 'https://jitpack.io' }
    }
}

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.0-beta03'
    }
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "godau.fynn.dsbdirect"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 34
        versionName "3.0"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        debug {
            versionNameSuffix " debug"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    flavorDimensions "store"
    productFlavors {
        fdroid {
            dimension "store"
        }
        notabug {
            versionNameSuffix " notabug"
            dimension "store"
        }
        play {
            applicationId "ga.testapp.dsbdirect"

            versionNameSuffix " play"
            dimension "store"
        }
    }

}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'com.wefika:horizontal-picker:1.1.1'
    implementation 'org.jsoup:jsoup:1.12.1'
    implementation 'com.github.mfornos:humanize-slim:1.2.2'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.github.MikeOrtiz:TouchImageView:1.4.0'
    implementation 'org.sufficientlysecure:html-textview:3.6'
    implementation 'com.github.DASAR:ShiftColorPicker:v0.5'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'androidx.fragment:fragment:1.2.0'
    implementation 'com.google.android.material:material:1.0.0'
}


code:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="settings_view_parser_options">
        <item>Automatic</item>
        <item>Untis</item>
        <item>HeinekingMedia (Goethe)</item>
        <item>WhiteYellow</item>
        <item>Dummy</item>
    </string-array>
    <string-array name="settings_view_parser_values">
        <item>automatic</item>
        <item>untis</item>
        <item>goethe</item>
        <item>whiteyellow</item>
        <item>dummy</item>
    </string-array>
</resources>


code:
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ DSBDirect
  ~ Copyright (C) 2019 Fynn Godau
  ~
  ~ This program is free software: you can redistribute it and/or modify
  ~ it under the terms of the GNU General Public License as published by
  ~ the Free Software Foundation, either version 3 of the License, or
  ~ (at your option) any later version.
  ~
  ~ This program 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 General Public License for more details.
  ~
  ~ You should have received a copy of the GNU General Public License
  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
  ~
  ~ This software is not affiliated with heinekingmedia GmbH, the
  ~ developer of the DSB platform.
  -->

<resources>
    <bool name="authenticate_quit_on_cancel">false</bool>
    <bool name="school_name_as_window_title_default">true</bool>
    <bool name="news_check_button">true</bool>
</resources>


code:
<?xml version="1.0" encoding="utf-8"?><!--
  ~ DSBDirect
  ~ Copyright (C) 2019 Fynn Godau
  ~
  ~ This program is free software: you can redistribute it and/or modify
  ~ it under the terms of the GNU General Public License as published by
  ~ the Free Software Foundation, either version 3 of the License, or
  ~ (at your option) any later version.
  ~
  ~ This program 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 General Public License for more details.
  ~
  ~ You should have received a copy of the GNU General Public License
  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
  ~
  ~ This software is not affiliated with heinekingmedia GmbH, the
  ~ developer of the DSB platform.
  -->

<resources>
    <string name="update_check_frequency_default" translatable="false">never</string>
    <string name="update_method" translatable="false">fdroid</string>
</resources>


code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="godau.fynn.dsbdirect">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Startup">
        <activity
            android:name=".activity.NoticeBoardActivity"
            android:label="@string/notice_board"
            android:theme="@style/AppTheme.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".activity.MainActivity" />
        </activity>
        <activity
            android:name=".activity.AboutLibrariesActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
        <activity
            android:name=".activity.LoginActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:exported="false"
            android:theme="@style/LoginTheme" />
        <activity
            android:name=".activity.MainActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:launchMode="singleTop"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".activity.settings.MainSettingsActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:exported="false"
            android:label="@string/title_activity_settings"
            android:parentActivityName=".activity.MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="godau.fynn.dsbdirect.activity.MainActivity" />
        </activity>
        <activity
            android:name=".activity.settings.StyleSettingsActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:exported="false"
            android:label="@string/settings_view_styling"
            android:parentActivityName=".activity.settings.MainSettingsActivity" />
        <activity
            android:name=".activity.settings.ShortcodeSettingsActivtiy"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:exported="false"
            android:label="@string/settings_shortcodes"
            android:parentActivityName=".activity.settings.MainSettingsActivity" />
        <activity
            android:name=".activity.settings.LoginSettingsActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:exported="false"
            android:label="@string/settings_login_manage"
            android:parentActivityName=".activity.settings.MainSettingsActivity" />

        <service
            android:name=".table.PollingService"
            android:exported="false"
            android:permission="android.permission.BIND_JOB_SERVICE" />
    </application>

</manifest>


code:
package godau.fynn.dsbdirect;

import org.json.JSONException;
import org.json.JSONObject;

import javax.annotation.Nullable;

public class Login {

    private String id, pass;
    @Nullable private String displayName;

    public Login(String id, String pass) {
        this.id = id;
        this.pass = pass;
    }

    public Login(String id, String pass, String displayName) {
        this.id = id;
        this.pass = pass;
        this.displayName = displayName;
    }

    /**
     * Deserialize a serialized login in this format: {@code "display name" id / pass}
     * @param serialized A String in the above format
     */
    public Login(String serialized) {

        if (serialized.startsWith("\"")) {
            String[] split = serialized.split("\"");
            displayName = split[1];
            serialized = serialized.replace("\"" + displayName + "\" ", "");
        }

        String[] split = serialized.split(" / ");

        // Check whether amount is valid
        if (split.length < 2) {
            throw new IllegalArgumentException("serialized string does not contain at least one \" / \" divider");
        }

        pass = "";

        for (String s :
                split) {
            if (id == null) id = s;
            else pass = pass + s;
        }

    }

    /**
     * @return The name that this login should be displayed as
     */
    public String getDisplayName() {
        if (displayName != null) {
            return displayName;
        } else {
            return id;
        }
    }

    /**
     * @return Whether {@link #displayName} is not null
     */
    public boolean hasDisplayName() {
        return displayName != null;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getId() {
        return id;
    }

    /**
     * @return {@code true} if {@link #id} and {@link #pass} both have a length larger than 0, otherwise {@code false}
     */
    public boolean isNonZeroLength() {
        return id.length() > 0 && pass.length() > 0;
    }

    /**
     * Inserts credentials into query json body
     * @param body Body to insert credentials into
     */
    public void put(JSONObject body) throws JSONException {
        body.put("UserId", id)
                .put("UserPw", pass);

    }

    /**
     * Serialize this login to a String
     * @return The login serialized to the format {@code "displayName" id / pass} or {@code id / pass}
     */
    public String serialize() {
        if (displayName == null) {
            return id + " / " + pass;
        } else {
            return "\"" + displayName + "\" " + id + " / " + pass;
        }
    }

    /**
     * @return {@code true} if object is a login with the same id, otherwise {@code super.equals(obj)}.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Login) {
            return ((Login) obj).getId().equals(id);
        } else {
            return super.equals(obj);
        }
    }
}


code:
package godau.fynn.dsbdirect;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import godau.fynn.dsbdirect.manager.DownloadManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

public class NewsQuery implements Runnable {

    /*
     * News structure:
     * {
     *  "statusMessage": "Message for the user",
     *  "statusMessageDE": "Nachricht an Nutzer",
     *  "queryBodyBaseJson": "optional: das neue queryBodyBaseJson",
     *  "querySendAppId": boolean, optional: ob die Client irgendeine AppId generieren soll
     *  "querySendAndroidVersion": boolean, optional: ob die Client sich irgendeine Android-Version aussuchen soll
     *  "querySendDeviceModel": boolean, optional: ob die Client sich irgendein Gerätemodell aussuchen soll
     *  "querySendLanguage": boolean, optional: ob die Client sich eine Sprache aussuchen soll
     *  "querySendDate": boolean, optional
     *  "querySendLastDate": boolean, optional
     *  "queryHeaders": ["optional", "Array mit headers"]
     *  "queryEndpoint": 0 (mobile) / 1 (web) / 2 (ihkmobile) / 3 (appihkbb)
     * }
     */

    Activity context;
    DownloadManager downloadManager;
    Utility u;

    public NewsQuery(Activity context, DownloadManager downloadManager) {
        this.context = context;
        this.downloadManager = downloadManager;
        u = new Utility(context);
    }

    @Override
    public void run() {
        try {
            JSONObject news = downloadManager.downloadNews();
            SharedPreferences sharedPreferences = u.getSharedPreferences();

            // Get message from news

            String message;

            // German or not german (hardcoded and uncool)
            if (Locale.getDefault().toString().contains("de")) {
                message = news.getString("statusMessageDE");
            } else {
                message = news.getString("statusMessage");
            }

            // Get new query body base json
            if (news.has("queryBodyBaseJson")) {
                sharedPreferences
                        .edit()
                        .putString("queryBodyBaseJson", news.getString("queryBodyBaseJson"))
                        .apply();
            }

            // Get booleans
            saveBoolean(news, "querySendAppId", sharedPreferences);
            saveBoolean(news, "querySendAndroidVersion", sharedPreferences);
            saveBoolean(news, "querySendDeviceModel", sharedPreferences);
            saveBoolean(news, "querySendLanguage", sharedPreferences);
            saveBoolean(news, "querySendDate", sharedPreferences);
            saveBoolean(news, "querySendLastDate", sharedPreferences);

            // Get new headers
            if (news.has("queryHeaders")) {
                JSONArray headers = news.getJSONArray("queryHeaders");

                Set<String> headerSet = new HashSet<String>();

                for (int i = 0; i < headers.length(); i++) {
                    String header = headers.getString(i);
                    headerSet.add(header);
                }

                u.getSharedPreferences()
                        .edit()
                        .putStringSet("queryHeaders", headerSet)
                        .apply();
            }

            // Get new endpoint
            if (news.has("queryEndpoint")) {
                int endpoint = news.getInt("queryEndpoint");

                u.getSharedPreferences()
                        .edit()
                        .putInt("queryEndpoint", endpoint)
                        .apply();
            }

            // Show news
            popup(message);

        } catch (JSONException | DownloadManager.UnexpectedResponseException ex) {
            popup(context.getString(R.string.news_network_invalid_response));
            ex.printStackTrace();
        } catch (IOException ex) {
            popup(context.getString(R.string.news_network_error_generic));
            ex.printStackTrace();
        }
    }

    private void saveBoolean(JSONObject news, String name, SharedPreferences sharedPreferences) throws JSONException {
        if (news.has(name)) {
            sharedPreferences
                    .edit()
                    .putBoolean(name, news.getBoolean(name))
                    .apply();
        }
    }

    private void popup(final String message) {
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                new AlertDialog.Builder(context)
                        .setMessage(message)
                        .show();
            }
        });

    }

    public static void wipeNews(Context context) {
        new Utility(context).getSharedPreferences().edit()
                .remove("queryBodyBaseJson")
                .remove("querySendDate")
                .remove("querySendLastDate")
                .remove("queryHeaders")
                .remove("queryEndpoint")
                .apply();
    }
}


code:
/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

package godau.fynn.dsbdirect;

import android.content.Context;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

/**
 * To color the title programmatically. See https://stackoverflow.com/a/11607334
 */
public class PreferenceCategory extends android.preference.PreferenceCategory {

    private Context context;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public PreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
    }

    public PreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    public PreferenceCategory(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public PreferenceCategory(Context context) {
        super(context);
        this.context = context;
    }

    @Override
    protected void onBindView(View view) {
        super.onBindView(view);

        // Can't test below lollipop but believing that it doesn't make sense below
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            TextView titleView = view.findViewById(android.R.id.title);
            titleView.setTextColor(new Utility(context).getColorAccent());
        }
    }

}


code:
package godau.fynn.dsbdirect;

import java.util.Random;
import java.util.UUID;

public abstract class QueryMetadata {

    /**
     * @return Some existing device model
     */
    public static String getDeviceModel() {
        final String[] DEVICE_LIST = {
                "GM1910",
                "GM1911",
                "GM1913",
                "GM1915",
                "GM1917",
                "G020E",
                "G020F",
                "G020G",
                "G020H",
                "PH-1",
                "VTR-AL00",
                "VTR-L09",
                "VTR-L29",
                "VTR-TL00",
                "TA-1181",
                "TA-1196",
                "J9150",
                "I3113",
                "I3123",
                "I4113",
                "I4193",
                "HTV33",
                "2PZC5"
        };

        return DEVICE_LIST[new Random().nextInt(DEVICE_LIST.length)];
    }

    /**
     * @return Some valid combination of API level and Android version
     */
    public static String getAndroidVersion() {
        final String[] VERSION_LIST = {
                "14 4.0.2",
                "15 4.0.4",
                "16 4.1.1",
                "17 4.2.2",
                "18 4.3",
                "19 4.4",
                "21 5.0",
                "22 5.1",
                "23 6.0.1",
                "24 7.0",
                "25 7.1.1",
                "26 8.0",
                "27 8.1",
                "28 9",
                "29 10.0"
        };

        return VERSION_LIST[new Random().nextInt(VERSION_LIST.length)];

    }

    /**
     * @return A language String, either en or (more likely) de
     */
    public static String getLanguage() {
        if (Math.random() > 0.9) return "en"; else return "de";
    }

    public static String getAppId() {
        // Generate AppId
        return UUID.randomUUID().toString();
    }
}


code:
/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

package godau.fynn.dsbdirect;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.*;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StyleRes;
import com.google.android.material.snackbar.Snackbar;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import godau.fynn.dsbdirect.manager.DownloadManager;
import godau.fynn.dsbdirect.table.PollingService;
import godau.fynn.dsbdirect.table.reader.*;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

import static android.content.Context.MODE_PRIVATE;

public class Utility {

    public static final String DATE_FORMAT = "EEEE, d.M.yyyy";

    /**
     * For Gadgetbridge compatibility: Gadgetbridge seems to only push content text, not big text text
     */
    public static final String SUPER_SECRET_SETTING_TEXT_AS_CONTENT_TEXT = "textAsContentText";

    /**
     * For debugging: display messages even if the exact same content has already been displayed
     */
    public static final String SUPER_SECRET_SETTING_NOTIFY_ABOUT_NOTHING_NEW = "ignoreAlreadyDisplayed";

    /**
     * For debugging: also poll on weekends
     */
    public static final String SUPER_SECRET_SETTING_POLL_ON_WEEKENDS = "ignoreWeekend";

    /**
     * For convenience: in case you find sharing annoying, you can disable it
     */

    public static final String SUPER_SECRET_SETTING_HOLD_TO_SHARE = "holdToShare";

    /**
     * For remembering which school you're at: display school name as window title even if there is only one login
     */
    public static final String SUPER_SECRET_SETTING_FORCE_SCHOOL_NAME_AS_WINDOW_TITLE = "schoolNameAsWindowTitle";

    private Context mContext;

    public Utility(Context context) {
        this.mContext = context;
    }

    public String formatDate(Date date) {
        Calendar today = Calendar.getInstance();

        Calendar then = Calendar.getInstance();
        then.setTime(date);

        if (then.get(Calendar.YEAR) == today.get(Calendar.YEAR)
        && then.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.today);
        }

        Calendar tomorrow = Calendar.getInstance();
        tomorrow.add(Calendar.DAY_OF_YEAR, 1);

        if (then.get(Calendar.YEAR) == tomorrow.get(Calendar.YEAR)
                && then.get(Calendar.DAY_OF_YEAR) == tomorrow.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.tomorrow);
        }

        Calendar yesterday = Calendar.getInstance();
        yesterday.add(Calendar.DAY_OF_YEAR, -1);

        if (then.get(Calendar.YEAR) == yesterday.get(Calendar.YEAR)
                && then.get(Calendar.DAY_OF_YEAR) == yesterday.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.yesterday);
        }

        // Date is neither today, yesterday nor tomorrow, format it

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault());
        return simpleDateFormat.format(date);
    }

    public @Nullable Reader getReader(String html, String id) {
        return ReaderFactory.getReader(html, id, mContext);
    }

    /**
     * Parse a natural date as provided as a last updated date
     * @param input A date in the format MM.dd.yyyy HH:mm
     * @return A Date
     */
    public static Date parseLastUpdatedDate(String input) {
        String[] dateParts = input.split(" ");
        String[] dateDigits = dateParts[0].split("\\.");
        String[] timeDigits = dateParts[1].split(":");
        return new Date(Integer.parseInt(dateDigits[2]) - 1900 /* years start at 1900 */, Integer.parseInt(dateDigits[1]) - 1 /* months start with 0 */,
                Integer.parseInt(dateDigits[0]), Integer.parseInt(timeDigits[0]), Integer.parseInt(timeDigits[1])); // because SimpleDateTime did deliver good results, again
    }

    public void checkForUpdate(final boolean verbose, final DownloadManager downloadManager) {
        // Thanks, https://stackoverflow.com/a/5069354
        final Activity activity = (Activity) mContext;
        final View rootView = ((ViewGroup) activity
                .findViewById(android.R.id.content)).getChildAt(0);

        Log.d("LOCALE", Locale.getDefault().toString());

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // Request current version metadata
                    JSONObject jsonObject = downloadManager.downloadUpdateCheck();

                    int currentVersion = BuildConfig.VERSION_CODE;

                    // Let's parse

                    int latestVersion = jsonObject.getInt("versionCode");
                    final String versionName = jsonObject.getString("versionName");
                    final String whatsNew;

                    // Localize changelog to German (hardcoded and uncool)
                    if (Locale.getDefault().toString().contains("de")) {
                        whatsNew = jsonObject.getString("whatsNewDE");
                    } else {
                        whatsNew = jsonObject.getString("whatsNew");
                    }

                    final String uri = jsonObject.getString("url");

                    if (latestVersion > currentVersion) {
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                new AlertDialog.Builder(mContext)
                                        .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                Intent intent;
                                                switch (mContext.getString(R.string.update_method)) {
                                                    default:
                                                    case "self":
                                                        // Download file provided in updateCheck
                                                        intent = new Intent(Intent.ACTION_VIEW)
                                                                .setDataAndType(Uri.parse(uri), "application/vnd.android.package-archive");
                                                        break;
                                                    case "fdroid":
                                                        // Launch fdroid website link and hope fdroid client handles it
                                                        String fdroidUri = mContext.getString(R.string.uri_fdroid, mContext.getPackageName());
                                                        intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fdroidUri));
                                                        break;
                                                    case "market":
                                                        // Launch market to download update
                                                        String marketUri = mContext.getString(R.string.uri_market, mContext.getPackageName());
                                                        intent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
                                                        break;
                                                }
                                                try {
                                                    mContext.startActivity(intent);
                                                } catch (ActivityNotFoundException e) {
                                                    mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(uri)));
                                                }
                                            }
                                        })
                                        .setNegativeButton(R.string.dismiss, null)
                                        .setTitle(mContext.getString(R.string.update_popup_title, versionName))
                                        .setMessage(whatsNew)
                                        .show();
                            }
                        });

                    } else {
                        if (verbose) {
                            activity.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Snackbar
                                            .make(rootView, R.string.up_to_date, Snackbar.LENGTH_SHORT)
                                            .show();
                                }
                            });
                        }
                    }

                } catch (IOException | JSONException e) {
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Snackbar.make(rootView, R.string.network_versioncheck_failed, Snackbar.LENGTH_SHORT)
                                    .show();
                        }
                    });
                    e.printStackTrace();
                }

            }
        }).start();

    }

    public SharedPreferences getSharedPreferences() {
        return mContext.getSharedPreferences("default", MODE_PRIVATE);
    }

    public String getUpdateCheckFrequency() {
        return getSharedPreferences().getString("update_check_frequency",
                mContext.getString(R.string.update_check_frequency_default));
    }

    public boolean shouldCheckForUpdates() {
        switch (getUpdateCheckFrequency()) {
            case "always":
                return true;
            case "sometimes":
                return new Random().nextInt(10) == 0;
            case "never":
            default:
                return false;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void schedulePolling() {
        // Check whether polling is enabled
        SharedPreferences sharedPreferences = getSharedPreferences();
        if (sharedPreferences.getBoolean("poll", false)) {
            // Polling is enabled

            ComponentName serviceComponent = new ComponentName(mContext, PollingService.class);
            JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
            builder.setMinimumLatency(PollingService.getNextPollingTimeDelay(sharedPreferences)); // Poll after at least this time
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // Only execute once network is connected
            builder.setPersisted(true);
            JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.schedule(builder.build());
        } else {
            // Cancel potential job
            JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.cancel(0);
        }
    }

    /**
     * @return A String containing String representations of all array elements separated by comma
     */
    public static <T> String smartConcatenate(T[] array, String comma) {
        StringBuilder result = new StringBuilder();

        // Remove empty strings
        ArrayList<T> list = new ArrayList<>(Arrays.asList(array));
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i) == null || list.get(i).equals("") || list.get(i).equals("<strike></strike>")) {
                list.remove(i);
            }
        }

        // Append remaining strings to result
        for (int i = 0; i < list.size(); i++) {
            T t = list.get(i);

            if (t instanceof Element) {
                result.append(((Element) t).text().replaceAll("\\\\n", "\n"));
            } else
                result.append(t.toString());

            if (i != list.size() - 1) {
                // This is not the last iteration yet
                result.append(comma);
            }

        }

        return String.valueOf(result);
    }

    public static Calendar zeroOClock(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar;
    }

    /**
     * @author https://stackoverflow.com/a/9904844
     */
    public int dpToPx(int dp) {
        // Get the screen's density scale
        final float scale = mContext.getResources().getDisplayMetrics().density;
        // Convert the dps to pixels, based on density scale
        return (int) (dp * scale + 0.5f);
    }

    public @StyleRes
    int getStyle() {
        String preference = getSharedPreferences().getString("style", "light");

        switch (preference) {
            case "light":
            default:
                return R.style.Light;
            case "dark":
                return R.style.Dark;
            case "black":
                return R.style.Black;
        }
    }

    public @ColorInt int getColorPrimary() {
        return getSharedPreferences().getInt("colorPrimary",
                mContext.getResources().getColor(R.color.colorPrimary));
    }

    public @ColorInt int getColorPrimaryDark() {
        return getSharedPreferences().getInt("colorPrimaryDark",
                mContext.getResources().getColor(R.color.colorPrimaryDark));
    }

    public @ColorInt int getColorAccent() {
        return getSharedPreferences().getInt("colorAccent",
                mContext.getResources().getColor(R.color.colorAccent));
    }

    public void stylize() {
        mContext.setTheme(getStyle());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            @ColorInt int colorPrimaryDark = getColorPrimaryDark();
            Window window = ((Activity) mContext).getWindow();
            window.setNavigationBarColor(colorPrimaryDark);
            window.setStatusBarColor(colorPrimaryDark);
        }
    }

}