├── gui ├── core │ ├── anirem.ico │ ├── anirem-core.qrc │ ├── qpmx.json │ ├── aniremsettingsviewmodel.h │ ├── aniremapp.h │ ├── imageloader.h │ ├── datasyncsettingsviewmodel.h │ ├── aniremsettingsviewmodel.cpp │ ├── detailsviewmodel.h │ ├── entryviewmodel.h │ ├── core.pro │ ├── datasyncsettingsviewmodel.cpp │ ├── loginviewmodel.h │ ├── proxerentrymodel.h │ ├── addanimeviewmodel.h │ ├── entryviewmodel.cpp │ ├── mainviewmodel.h │ ├── detailsviewmodel.cpp │ ├── imageloader.cpp │ └── loginviewmodel.cpp ├── quick │ ├── openssl │ │ ├── .gitignore │ │ └── get_openssl.sh │ ├── qt.conf │ ├── anirem-android.qrc │ ├── android │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_notification.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_notification.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_notification.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_notification.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_notification.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_foreground.png │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── strings.xml │ │ │ │ └── libs.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ └── ic_launcher.xml │ │ │ └── values-de │ │ │ │ └── strings.xml │ │ ├── src │ │ │ └── de │ │ │ │ └── skycoder42 │ │ │ │ └── anirem │ │ │ │ ├── Globals.java │ │ │ │ ├── BootReceiver.java │ │ │ │ ├── AniremActivity.java │ │ │ │ └── AniremService.java │ │ └── build.gradle │ ├── App.qml │ ├── qtquickcontrols2.conf │ ├── icons │ │ ├── ic_add_white_24px.svg │ │ ├── ic_check_white_24px.svg │ │ ├── ic_filter_list_white_24px.svg │ │ ├── ic_arrow_back_white_24px.svg │ │ ├── ic_playlist_add_white_24px.svg │ │ ├── ic_open_in_browser_white_24px.svg │ │ ├── ic_content_paste_white_24px.svg │ │ ├── ic_autorenew_white_24px.svg │ │ ├── ic_text_fields_white_24px.svg │ │ └── ic_delete_forever_white_24px.svg │ ├── qtquickcontrols2_android.conf │ ├── qtquickcontrols2_win.conf │ ├── qpmx.json │ ├── qmltoast.h │ ├── anirem_de.ts │ ├── qmltoast.cpp │ ├── proxerimageprovider.h │ ├── AnimeHistoryDelegate.qml │ ├── EntryView.qml │ ├── anirem-quick.qrc │ ├── SubButton.qml │ ├── AnimeInfoDelegate.qml │ ├── proxerimageprovider.cpp │ ├── DetailsView.qml │ ├── AddAnimeDialog.qml │ ├── LoginDialog.qml │ ├── quick.pro │ └── main.cpp ├── widgets │ ├── installer │ │ ├── main.png │ │ ├── meta │ │ │ └── package.xml │ │ ├── anirem_de.ts │ │ └── config.xml │ ├── resources │ │ ├── bell.ico │ │ ├── cross.ico │ │ ├── plus.ico │ │ ├── world_go.ico │ │ ├── bell_valid.ico │ │ ├── ajax-loader.gif │ │ ├── arrow_refresh.ico │ │ ├── paste_plain.ico │ │ ├── textfield_add.ico │ │ ├── anirem_inverted.ico │ │ ├── page_white_copy.ico │ │ └── scroll_pane_list_add.ico │ ├── icons │ │ └── hicolor │ │ │ ├── 128x128 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 16x16 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 192x192 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 22x22 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 24x24 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 256x256 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 32x32 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 48x48 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 512x512 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 64x64 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ ├── 72x72 │ │ │ └── apps │ │ │ │ └── anirem.png │ │ │ └── 96x96 │ │ │ └── apps │ │ │ └── anirem.png │ ├── instancesetup.h │ ├── animemodel.h │ ├── logindialog.h │ ├── entrydialog.h │ ├── anirem.desktop │ ├── anirem_de.ts │ ├── detailsdockwidget.h │ ├── qpmx.json │ ├── anirem-widgets.qrc │ ├── widgetsupdatenotifier.h │ ├── addanimedialog.h │ ├── entrydialog.cpp │ ├── logindialog.cpp │ ├── mainwindow.h │ ├── animemodel.cpp │ ├── main.cpp │ ├── detailsdockwidget.cpp │ ├── widgetsupdatenotifier.cpp │ ├── entrydialog.ui │ ├── addanimedialog.cpp │ └── detailsdockwidget.ui └── gui.pro ├── ci ├── anirem.aks ├── proxer.data ├── appveyor_init.bat ├── appveyor_postbuild.bat ├── travis_init.sh ├── travis_deploy.sh ├── pkgbuild.sh └── travis_postbuild.sh ├── icn ├── anirem.ico ├── anirem.icns ├── store_fn.png ├── store_ic.png ├── anirem_white.svg └── anirem.svg ├── img ├── desktop_add.png ├── mobile_add.png ├── mobile_main.png ├── desktop_main.png └── mobile_detail.png ├── droid-service ├── android │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── splash.9.png │ │ │ └── ic_notification.png │ │ ├── drawable-mdpi │ │ │ ├── splash.9.png │ │ │ └── ic_notification.png │ │ ├── drawable-xhdpi │ │ │ ├── splash.9.png │ │ │ └── ic_notification.png │ │ ├── drawable-xxhdpi │ │ │ ├── splash.9.png │ │ │ └── ic_notification.png │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_foreground.png │ │ ├── drawable-xxxhdpi │ │ │ ├── splash.9.png │ │ │ └── ic_notification.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_foreground.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── styles.xml │ │ │ ├── strings.xml │ │ │ └── libs.xml │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ └── values-de │ │ │ └── strings.xml │ ├── src │ │ └── de │ │ │ └── skycoder42 │ │ │ └── anirem │ │ │ ├── Globals.java │ │ │ ├── AniremService.java │ │ │ ├── BootReceiver.java │ │ │ └── AniremActivity.java │ └── build.gradle ├── droid-service.pro ├── starthelper.h ├── androidupdatenotifier.h ├── androidupdatenotifier.cpp ├── main.cpp └── starthelper.cpp ├── lib ├── syncedsettings.xml ├── api │ ├── proxerloginvalue.xml │ ├── proxernamevalue.xml │ ├── proxerstatus.xml │ ├── proxerentry.xml │ ├── proxerlogin.xml │ ├── proxerlist.xml │ ├── proxernames.xml │ ├── proxerrelations.xml │ ├── proxerentryvalue.xml │ ├── proxerlistvalue.xml │ ├── proxerapi.xml │ ├── infoclass.xml │ └── userclass.xml ├── lib_anirem_global.h ├── qpmx.json ├── apihelper.h ├── libanirem.h ├── jsonseasondataconverter.h ├── storedcookiejar.h ├── iupdatenotifier.h ├── localsettings.xml ├── passiveupdater.h ├── seasonstatusloader.h ├── apihelper.cpp ├── jsonseasondataconverter.cpp ├── storedcookiejar.cpp ├── passiveupdater.cpp ├── animeinfo.h ├── libanirem.cpp └── lib.pro ├── lib.pri ├── AniRem.pro ├── appveyor.yml ├── .gitignore ├── LICENSE ├── .qmake.conf └── .travis.yml /gui/core/anirem.ico: -------------------------------------------------------------------------------- 1 | ../../icn/anirem.ico -------------------------------------------------------------------------------- /gui/quick/openssl/.gitignore: -------------------------------------------------------------------------------- 1 | openssl* 2 | -------------------------------------------------------------------------------- /gui/quick/qt.conf: -------------------------------------------------------------------------------- 1 | [Paths] 2 | Translations=assets:/translations 3 | -------------------------------------------------------------------------------- /ci/anirem.aks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/ci/anirem.aks -------------------------------------------------------------------------------- /ci/proxer.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/ci/proxer.data -------------------------------------------------------------------------------- /icn/anirem.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/icn/anirem.ico -------------------------------------------------------------------------------- /icn/anirem.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/icn/anirem.icns -------------------------------------------------------------------------------- /icn/store_fn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/icn/store_fn.png -------------------------------------------------------------------------------- /icn/store_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/icn/store_ic.png -------------------------------------------------------------------------------- /img/desktop_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/img/desktop_add.png -------------------------------------------------------------------------------- /img/mobile_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/img/mobile_add.png -------------------------------------------------------------------------------- /img/mobile_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/img/mobile_main.png -------------------------------------------------------------------------------- /img/desktop_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/img/desktop_main.png -------------------------------------------------------------------------------- /img/mobile_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/img/mobile_detail.png -------------------------------------------------------------------------------- /gui/widgets/installer/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/installer/main.png -------------------------------------------------------------------------------- /gui/widgets/resources/bell.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/bell.ico -------------------------------------------------------------------------------- /gui/widgets/resources/cross.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/cross.ico -------------------------------------------------------------------------------- /gui/widgets/resources/plus.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/plus.ico -------------------------------------------------------------------------------- /gui/widgets/resources/world_go.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/world_go.ico -------------------------------------------------------------------------------- /ci/appveyor_init.bat: -------------------------------------------------------------------------------- 1 | 7z x "-p%PROXER_DATA%" -o. ".\ci\proxer.data" > nul 2 | echo CONFIG += create_installer >> install.pri 3 | -------------------------------------------------------------------------------- /gui/widgets/resources/bell_valid.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/bell_valid.ico -------------------------------------------------------------------------------- /gui/widgets/resources/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/ajax-loader.gif -------------------------------------------------------------------------------- /gui/widgets/resources/arrow_refresh.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/arrow_refresh.ico -------------------------------------------------------------------------------- /gui/widgets/resources/paste_plain.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/paste_plain.ico -------------------------------------------------------------------------------- /gui/widgets/resources/textfield_add.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/textfield_add.ico -------------------------------------------------------------------------------- /gui/widgets/resources/anirem_inverted.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/anirem_inverted.ico -------------------------------------------------------------------------------- /gui/widgets/resources/page_white_copy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/page_white_copy.ico -------------------------------------------------------------------------------- /gui/quick/anirem-android.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qt.conf 4 | 5 | 6 | -------------------------------------------------------------------------------- /gui/widgets/resources/scroll_pane_list_add.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/resources/scroll_pane_list_add.ico -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-hdpi/splash.9.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-mdpi/splash.9.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xhdpi/splash.9.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/128x128/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/128x128/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/16x16/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/16x16/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/192x192/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/192x192/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/22x22/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/22x22/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/24x24/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/24x24/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/256x256/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/256x256/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/32x32/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/32x32/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/48x48/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/48x48/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/512x512/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/512x512/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/64x64/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/64x64/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/72x72/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/72x72/apps/anirem.png -------------------------------------------------------------------------------- /gui/widgets/icons/hicolor/96x96/apps/anirem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/widgets/icons/hicolor/96x96/apps/anirem.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-hdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-hdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-mdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-mdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-hdpi/splash.9.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-mdpi/splash.9.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xhdpi/splash.9.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xhdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xxhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xxhdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xxxhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xxxhdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-hdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-hdpi/ic_foreground.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-mdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-mdpi/ic_foreground.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xhdpi/ic_foreground.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /droid-service/android/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4E4E4E 4 | 5 | -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /gui/quick/android/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4E4E4E 4 | 5 | -------------------------------------------------------------------------------- /droid-service/android/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xxhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xxhdpi/ic_foreground.png -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-xxxhdpi/ic_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/mipmap-xxxhdpi/ic_foreground.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /gui/quick/android/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/gui/quick/android/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /droid-service/android/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/AniRem/master/droid-service/android/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /gui/quick/App.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import de.skycoder42.QtMvvm.Quick 1.0 3 | import de.skycoder42.QtMvvm.DataSync.Quick 1.0 4 | 5 | QtMvvmApp { 6 | title: qsTr("Ani-Rem") 7 | } 8 | -------------------------------------------------------------------------------- /gui/quick/qtquickcontrols2.conf: -------------------------------------------------------------------------------- 1 | [Material] 2 | Theme=Dark 3 | Primary=#777777 4 | Accent=#8A0E0E 5 | Background=#4E4E4E 6 | 7 | [Universal] 8 | Theme=Dark 9 | Accent=#8A0E0E 10 | Background=#4E4E4E 11 | -------------------------------------------------------------------------------- /gui/core/anirem-core.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | settings.xml 4 | 5 | 6 | anirem.ico 7 | 8 | 9 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/widgets/instancesetup.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTANCESETUP_H 2 | #define INSTANCESETUP_H 3 | 4 | #include 5 | 6 | void setMainWindow(QMainWindow *window); 7 | QMainWindow *getMainWindow(); 8 | 9 | #endif // INSTANCESETUP_H 10 | -------------------------------------------------------------------------------- /gui/quick/qtquickcontrols2_android.conf: -------------------------------------------------------------------------------- 1 | [Material] 2 | Theme=Dark 3 | Primary=#777777 4 | Accent=#8A0E0E 5 | Background=#4E4E4E 6 | 7 | [Universal] 8 | Theme=Dark 9 | Accent=#8A0E0E 10 | Background=#4E4E4E 11 | 12 | [Controls] 13 | Style=Material 14 | -------------------------------------------------------------------------------- /gui/quick/qtquickcontrols2_win.conf: -------------------------------------------------------------------------------- 1 | [Material] 2 | Theme=Dark 3 | Primary=#777777 4 | Accent=#8A0E0E 5 | Background=#4E4E4E 6 | 7 | [Universal] 8 | Theme=Dark 9 | Accent=#8A0E0E 10 | Background=#4E4E4E 11 | 12 | [Controls] 13 | Style=Universal 14 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_check_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_filter_list_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/quick/openssl/get_openssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | curl -Lo "openssl.tar.xz" "https://github.com/Skycoder42/ci-builds/releases/download/${OPENSSL_VERSION}/openssl_${OPENSSL_VERSION}_${PLATFORM}.tar.xz" 5 | tar -xf openssl.tar.xz 6 | rm -f openssl.tar.xz 7 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_arrow_back_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/core/qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | ], 4 | "license": { 5 | "file": "", 6 | "name": "" 7 | }, 8 | "prcFile": "", 9 | "priFile": "", 10 | "priIncludes": [ 11 | ], 12 | "publishers": { 13 | }, 14 | "source": false 15 | } 16 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_playlist_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/quick/qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | ], 4 | "license": { 5 | "file": "", 6 | "name": "" 7 | }, 8 | "prcFile": "", 9 | "priFile": "", 10 | "priIncludes": [ 11 | ], 12 | "publishers": { 13 | }, 14 | "source": false 15 | } 16 | -------------------------------------------------------------------------------- /gui/quick/android/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gui/quick/android/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem 4 | Überprüfe auf neue Seasons… 5 | Animes werden geladen… 6 | 7 | -------------------------------------------------------------------------------- /droid-service/android/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /droid-service/android/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem 4 | Überprüfe auf neue Seasons… 5 | Animes werden geladen… 6 | 7 | -------------------------------------------------------------------------------- /lib/syncedsettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_open_in_browser_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/gui.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | core \ 5 | widgets \ 6 | quick 7 | 8 | widgets.depends += core 9 | quick.depends += core 10 | 11 | android: SUBDIRS -= widgets 12 | no_quick: SUBDIRS -= quick 13 | 14 | prepareRecursiveTarget(lrelease) 15 | prepareRecursiveTarget(qtifw) 16 | QMAKE_EXTRA_TARGETS += lrelease qtifw 17 | -------------------------------------------------------------------------------- /lib/api/proxerloginvalue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/api/proxernamevalue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ci/appveyor_postbuild.bat: -------------------------------------------------------------------------------- 1 | setlocal 2 | 3 | set qtplatform=%PLATFORM% 4 | for %%* in (.) do set CurrDirName=%%~nx* 5 | 6 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /B 1 7 | 8 | cd build-%qtplatform% 9 | nmake INSTALL_ROOT=\projects\%CurrDirName%\install qtifw || exit /B 1 10 | -------------------------------------------------------------------------------- /lib/api/proxerstatus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /droid-service/droid-service.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += androidextras mvvmcore 4 | QT -= gui 5 | 6 | TARGET = anirem-service 7 | 8 | HEADERS += \ 9 | starthelper.h \ 10 | androidupdatenotifier.h 11 | 12 | SOURCES += \ 13 | main.cpp \ 14 | starthelper.cpp \ 15 | androidupdatenotifier.cpp 16 | 17 | QMAKE_EXTRA_TARGETS += lrelease qtifw 18 | 19 | include(../lib.pri) 20 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_content_paste_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gui/widgets/installer/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem Season Reminder 4 | Ani-Rem Season Reminder 5 | 2.0.0 6 | 2018-04-15 7 | true 8 | true 9 | de.skycoder42.seasonproxer 10 | 11 | -------------------------------------------------------------------------------- /lib/api/proxerentry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerstatus.h 5 | proxerentryvalue.h 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/api/proxerlogin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerstatus.h 5 | proxerloginvalue.h 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_autorenew_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/lib_anirem_global.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_ANIREM_GLOBAL_H 2 | #define LIB_ANIREM_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(BUILD_LIB_ANIREM) 7 | # define LIB_ANIREM_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define LIB_ANIREM_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #ifdef BUILD_LIB_ANIREM 13 | #ifndef PROXER_API_KEY 14 | #include "../proxer-api-key.h" 15 | #endif 16 | #endif 17 | 18 | #endif // LIB_ANIREM_GLOBAL_H 19 | -------------------------------------------------------------------------------- /lib/api/proxerlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerstatus.h 5 | proxerlistvalue.h 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/api/proxernames.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerstatus.h 5 | proxernamevalue.h 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_text_fields_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/api/proxerrelations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerstatus.h 5 | proxerentryvalue.h 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "package": "de.skycoder42.qsettingsgenerator", 5 | "provider": "qpm", 6 | "version": "1.4.1" 7 | } 8 | ], 9 | "license": { 10 | "file": "", 11 | "name": "" 12 | }, 13 | "prcFile": "", 14 | "priFile": "", 15 | "priIncludes": [ 16 | ], 17 | "publishers": { 18 | }, 19 | "source": false 20 | } 21 | -------------------------------------------------------------------------------- /gui/quick/icons/ic_delete_forever_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gui/quick/qmltoast.h: -------------------------------------------------------------------------------- 1 | #ifndef QMLTOAST_H 2 | #define QMLTOAST_H 3 | 4 | #include 5 | 6 | class QmlToast : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | enum Duration { 12 | Short = 0x00000000, 13 | Long = 0x00000001 14 | }; 15 | Q_ENUM(Duration) 16 | 17 | explicit QmlToast(QObject *parent = nullptr); 18 | 19 | public slots: 20 | void toast(const QString &text, Duration duration = Short); 21 | }; 22 | 23 | #endif // QMLTOAST_H 24 | -------------------------------------------------------------------------------- /lib/api/proxerentryvalue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gui/quick/android/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /lib/api/proxerlistvalue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /droid-service/android/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /gui/widgets/animemodel.h: -------------------------------------------------------------------------------- 1 | #ifndef ANIMEMODEL_H 2 | #define ANIMEMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class AnimeModel : public QObjectProxyModel 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit AnimeModel(QtDataSync::DataStoreModel *srcModel, QObject *parent = nullptr); 15 | 16 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 17 | }; 18 | 19 | #endif // ANIMEMODEL_H 20 | -------------------------------------------------------------------------------- /gui/widgets/logindialog.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGINDIALOG_H 2 | #define LOGINDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class LoginDialog; 9 | } 10 | 11 | class LoginDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | Q_INVOKABLE explicit LoginDialog(QtMvvm::ViewModel *viewModel, QWidget *parent = nullptr); 17 | ~LoginDialog() override; 18 | 19 | void accept() override; 20 | 21 | private: 22 | LoginViewModel *_viewModel; 23 | Ui::LoginDialog *_ui; 24 | }; 25 | 26 | #endif // LOGINDIALOG_H 27 | -------------------------------------------------------------------------------- /droid-service/starthelper.h: -------------------------------------------------------------------------------- 1 | #ifndef STARTHELPER_H 2 | #define STARTHELPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class StartHelper : public QObject 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit StartHelper(QObject *parent = nullptr); 14 | 15 | static void handleStart(); 16 | 17 | private slots: 18 | void startUpdater(); 19 | void done(); 20 | 21 | private: 22 | static QMutex _runMutex; 23 | static QPointer _runInstance; 24 | static bool _wasStarted; 25 | }; 26 | 27 | #endif // STARTHELPER_H 28 | -------------------------------------------------------------------------------- /lib.pri: -------------------------------------------------------------------------------- 1 | # link against main lib 2 | QT += datasync restclient mvvmcore 3 | 4 | ARLIB_DIR = $$shadowed($$PWD/lib) 5 | win32:CONFIG(release, debug|release): LIBS += -L$$ARLIB_DIR/release/ -lanirem 6 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$ARLIB_DIR/debug/ -lanirem 7 | else:mac: LIBS += -F$$ARLIB_DIR/ -framework anirem 8 | else: LIBS += -L$$ARLIB_DIR/ -lanirem 9 | 10 | INCLUDEPATH += $$PWD/lib $$ARLIB_DIR 11 | win32:CONFIG(release, debug|release): INCLUDEPATH += $$ARLIB_DIR/release 12 | else:win32:CONFIG(debug, debug|release): INCLUDEPATH += $$ARLIB_DIR/debug 13 | DEPENDPATH += $$PWD/lib 14 | -------------------------------------------------------------------------------- /gui/widgets/entrydialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTRYDIALOG_H 2 | #define ENTRYDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class EntryDialog; 9 | } 10 | 11 | class EntryDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | Q_INVOKABLE explicit EntryDialog(QtMvvm::ViewModel *viewModel, QWidget *parent = nullptr); 17 | ~EntryDialog() override; 18 | 19 | private slots: 20 | void on_treeView_activated(const QModelIndex &index); 21 | 22 | private: 23 | EntryViewModel *_viewModel; 24 | Ui::EntryDialog *_ui; 25 | }; 26 | 27 | #endif // ENTRYDIALOG_H 28 | -------------------------------------------------------------------------------- /lib/api/proxerapi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | infoclass.h 5 | userclass.h 6 | 7 | https://proxer.me/api 8 |
PROXER_API_KEY
9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /lib/apihelper.h: -------------------------------------------------------------------------------- 1 | #ifndef APIHELPER_H 2 | #define APIHELPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "lib_anirem_global.h" 9 | #include "proxerstatus.h" 10 | 11 | class LIB_ANIREM_EXPORT ApiHelper 12 | { 13 | Q_DECLARE_TR_FUNCTIONS(ApiHelper) 14 | 15 | public: 16 | static QString transformError(ProxerStatus status, int); 17 | static QString formatError(const QString &error, int errorCode, QtRestClient::RestReply::ErrorType type); 18 | static bool testValid(int code, ProxerStatus status); 19 | }; 20 | 21 | #endif // APIHELPER_H 22 | -------------------------------------------------------------------------------- /lib/libanirem.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBANIREM_H 2 | #define LIBANIREM_H 3 | 4 | #include 5 | #include 6 | 7 | #include "lib_anirem_global.h" 8 | #include "animeinfo.h" 9 | #include "localsettings.h" 10 | 11 | using AniremStore = QtDataSync::DataTypeStore; 12 | 13 | namespace AniRem { 14 | 15 | LIB_ANIREM_EXPORT void prepareTranslations(); 16 | LIB_ANIREM_EXPORT void setup(QtDataSync::Setup &setup, bool passive = false); 17 | LIB_ANIREM_EXPORT void setProxerToken(const QString &token, LocalSettings *settings); 18 | 19 | } 20 | 21 | #endif // LIBANIREM_H 22 | -------------------------------------------------------------------------------- /gui/widgets/anirem.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.1 4 | Name=Ani-Rem — Anime Season Reminder 5 | Name[de]=Ani-Rem — Anime Season Erinnerungen 6 | Comment=A tool to passivly check for updates on seasons, for proxer.me 7 | Comment[de]=Ein Tool zum passiven überprüfen von Updates für Anime Seasons, basierend auf proxer.me 8 | Exec=anirem 9 | Icon=anirem 10 | Terminal=false 11 | Categories=Utility;Qt; 12 | Actions=UpdateCheck; 13 | 14 | [Desktop Action UpdateCheck] 15 | Exec=anirem --update 16 | Name=Check for updated seasons 17 | Name[de]=Auf neue Seasons überprüfen 18 | Icon=view-refresh 19 | -------------------------------------------------------------------------------- /droid-service/android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem 4 | Checking for new seasons… 5 | Loading Animes… 6 | 7 | Anime Informations 8 | Notifications about new relations of your saved animes 9 | Service Status 10 | Status notifications that Ani-Rem is updating relations 11 | 12 | -------------------------------------------------------------------------------- /lib/jsonseasondataconverter.h: -------------------------------------------------------------------------------- 1 | #ifndef JSONSEASONDATACONVERTER_H 2 | #define JSONSEASONDATACONVERTER_H 3 | 4 | #include 5 | 6 | class JsonSeasonDataConverter : public QJsonTypeConverter 7 | { 8 | public: 9 | bool canConvert(int metaTypeId) const override; 10 | QList jsonTypes() const override; 11 | QJsonValue serialize(int propertyType, const QVariant &value, const SerializationHelper *helper) const override; 12 | QVariant deserialize(int propertyType, const QJsonValue &value, QObject *parent, const SerializationHelper *helper) const override; 13 | }; 14 | 15 | #endif // JSONSEASONDATACONVERTER_H 16 | -------------------------------------------------------------------------------- /gui/widgets/anirem_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/storedcookiejar.h: -------------------------------------------------------------------------------- 1 | #ifndef STOREDCOOKIEJAR_H 2 | #define STOREDCOOKIEJAR_H 3 | 4 | #include 5 | #include 6 | 7 | class StoredCookieJar : public QNetworkCookieJar 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit StoredCookieJar(QObject *parent = nullptr); 13 | ~StoredCookieJar(); 14 | 15 | bool insertCookie(const QNetworkCookie &cookie) override; 16 | bool deleteCookie(const QNetworkCookie &cookie) override; 17 | 18 | private slots: 19 | void store(); 20 | 21 | private: 22 | static const QString CookieKey; 23 | static const QString CookieDataKey; 24 | 25 | QSettings *_cookieStore; 26 | bool _scheduled; 27 | }; 28 | 29 | #endif // STOREDCOOKIEJAR_H 30 | -------------------------------------------------------------------------------- /gui/widgets/installer/anirem_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/iupdatenotifier.h: -------------------------------------------------------------------------------- 1 | #ifndef IUPDATENOTIFIER_H 2 | #define IUPDATENOTIFIER_H 3 | 4 | #include 5 | 6 | class IUpdateNotifier 7 | { 8 | Q_DISABLE_COPY(IUpdateNotifier) 9 | 10 | public: 11 | inline IUpdateNotifier() = default; 12 | virtual inline ~IUpdateNotifier() = default; 13 | 14 | public slots: 15 | virtual void showNotification(bool isPositive, const QString &title, const QString &description) = 0; 16 | virtual void updateProgress(int value, int max) = 0; 17 | }; 18 | 19 | #define IUpdateNotifierIid "de.skycoder42.anirem.IUpdateNotifier" 20 | Q_DECLARE_INTERFACE(IUpdateNotifier, IUpdateNotifierIid) 21 | Q_DECLARE_METATYPE(IUpdateNotifier*) 22 | 23 | #endif // IUPDATENOTIFIER_H 24 | -------------------------------------------------------------------------------- /gui/quick/android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem 4 | Checking for new seasons… 5 | Loading Animes… 6 | Checking Anime for updates: 7 | 8 | Anime Informations 9 | Notifications about new relations of your saved animes 10 | Service Status 11 | Status notifications that Ani-Rem is updating relations 12 | 13 | -------------------------------------------------------------------------------- /droid-service/androidupdatenotifier.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDUPDATENOTIFIER_H 2 | #define ANDROIDUPDATENOTIFIER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class AndroidUpdateNotifier : public QObject, public IUpdateNotifier 9 | { 10 | Q_OBJECT 11 | Q_INTERFACES(IUpdateNotifier) 12 | 13 | public: 14 | Q_INVOKABLE explicit AndroidUpdateNotifier(QObject *parent = nullptr); 15 | 16 | public slots: 17 | void showNotification(bool isPositive, const QString &title, const QString &description) override; 18 | void updateProgress(int current, int max) override; 19 | 20 | private: 21 | QAndroidJniObject _service; 22 | }; 23 | 24 | #endif // ANDROIDUPDATENOTIFIER_H 25 | -------------------------------------------------------------------------------- /gui/quick/anirem_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/localsettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | QSize 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ci/travis_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | currDir=$(dirname $0) 5 | 6 | echo 'CONFIG += create_installer' >> install.pri 7 | 8 | # install 7z 9 | if [[ $TRAVIS_OS_NAME == "linux" ]]; then 10 | sudo apt-get -qq update 11 | sudo apt-get -qq install --no-install-recommends p7zip-full 12 | 13 | # append post build script 14 | echo "$currDir/travis_postbuild.sh" >> qtmodules-travis/ci/linux/build-docker.sh 15 | fi 16 | 17 | if [[ $TRAVIS_OS_NAME == "osx" ]]; then 18 | brew install p7zip 19 | fi 20 | 21 | # unpack data 22 | mkdir ../__private 23 | 7z x "-p$PROXER_DATA" -o. "./ci/proxer.data" > /dev/null 24 | 25 | # install openssl for android 26 | if [[ $PLATFORM == "android_"* ]]; then 27 | pushd gui/quick/openssl 28 | ./get_openssl.sh 29 | popd 30 | fi 31 | -------------------------------------------------------------------------------- /gui/core/aniremsettingsviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef ANIREMSETTINGSVIEWMODEL_H 2 | #define ANIREMSETTINGSVIEWMODEL_H 3 | 4 | #include "datasyncsettingsviewmodel.h" 5 | #include "imageloader.h" 6 | 7 | class AniremSettingsViewModel : public DataSyncSettingsViewModel 8 | { 9 | Q_OBJECT 10 | 11 | QTMVVM_INJECT_PROP(ImageLoader*, imageLoader, _loader) 12 | 13 | public: 14 | Q_INVOKABLE explicit AniremSettingsViewModel(QObject *parent = nullptr); 15 | 16 | void saveValue(const QString &key, const QVariant &value) override; 17 | void resetValue(const QString &key) override; 18 | 19 | public slots: 20 | void callAction(const QString &key, const QVariantMap ¶meters) override; 21 | 22 | private: 23 | ImageLoader *_loader; 24 | }; 25 | 26 | #endif // ANIREMSETTINGSVIEWMODEL_H 27 | -------------------------------------------------------------------------------- /gui/quick/android/res/values/libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://download.qt.io/ministro/android/qt5/qt-5.9 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /droid-service/android/res/values/libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://download.qt.io/ministro/android/qt5/qt-5.9 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AniRem.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | gui \ 5 | lib 6 | 7 | gui.depends += lib 8 | 9 | android { 10 | SUBDIRS += droid-service 11 | droid-service.depends += lib 12 | } 13 | 14 | prepareRecursiveTarget(lrelease) 15 | prepareRecursiveTarget(qtifw) 16 | QMAKE_EXTRA_TARGETS += lrelease qtifw 17 | 18 | DISTFILES += .qmake.conf \ 19 | README.md 20 | 21 | mac { 22 | install_join.commands += cp -pRv "$(INSTALL_ROOT)$$INSTALL_APPS/" "$(INSTALL_ROOT)$${INSTALL_PREFIX}/" \ 23 | $$escape_expand(\n\t)rm -rf "$(INSTALL_ROOT)$$INSTALL_APPS" \ 24 | $$escape_expand(\n\t)install_name_tool -change anirem.framework/Versions/2/anirem @rpath/anirem.framework/Versions/2/anirem "$(INSTALL_ROOT)$${INSTALL_BINS}/Ani-Rem" 25 | install_join.target = install 26 | QMAKE_EXTRA_TARGETS += install_join 27 | } 28 | -------------------------------------------------------------------------------- /gui/widgets/detailsdockwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef DETAILSDOCKWIDGET_H 2 | #define DETAILSDOCKWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class DetailsDockWidget; 9 | } 10 | 11 | class DetailsDockWidget : public QDockWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | Q_INVOKABLE DetailsDockWidget(QtMvvm::ViewModel *viewModel, QWidget *parent = nullptr); 17 | ~DetailsDockWidget() override; 18 | 19 | protected: 20 | void closeEvent(QCloseEvent *event) override; 21 | 22 | private slots: 23 | void updateInfo(); 24 | 25 | void imageLoaded(int id, const QImage &image); 26 | void imageLoadFailed(int id, const QString &error); 27 | 28 | private: 29 | DetailsViewModel *_viewModel; 30 | Ui::DetailsDockWidget *_ui; 31 | }; 32 | 33 | #endif // DETAILSDOCKWIDGET_H 34 | -------------------------------------------------------------------------------- /gui/core/aniremapp.h: -------------------------------------------------------------------------------- 1 | #ifndef GUIAPP_H 2 | #define GUIAPP_H 3 | 4 | #include 5 | 6 | class AniRemApp : public QtMvvm::CoreApp 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit AniRemApp(QObject *parent = nullptr); 12 | 13 | void updateAutoStartState(); 14 | void updateAutoStartState(int interval); 15 | 16 | signals: 17 | void updateMigrationProgressMax(int max); 18 | void updateMigrationProgressValue(int value); 19 | 20 | protected: 21 | void performRegistrations() override; 22 | int startApp(const QStringList &arguments) override; 23 | 24 | private slots: 25 | void migrate(); 26 | 27 | private: 28 | bool setAutoStart(bool autoStart); 29 | 30 | void updateCheck(); 31 | }; 32 | 33 | #undef coreApp 34 | #define coreApp static_cast(QtMvvm::CoreApp::instance()) 35 | 36 | #endif // GUIAPP_H 37 | -------------------------------------------------------------------------------- /gui/widgets/installer/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ani-Rem Season Reminder 4 | 2.0.0 5 | Ani-Rem Season Reminder Installer 6 | Sykcoder42 7 | https://github.com/Skycoder42/AniRem 8 | Ani-Rem 9 | Ani-Rem 10 | Ani-Rem 11 | true 12 | true 13 | 14 | 15 | https://install.skycoder42.de/installers/anirem 16 | 1 17 | Ani-Rem Season Reminder Repository 18 | 19 | 20 | controller.js 21 | 22 | -------------------------------------------------------------------------------- /droid-service/androidupdatenotifier.cpp: -------------------------------------------------------------------------------- 1 | #include "androidupdatenotifier.h" 2 | #include 3 | 4 | AndroidUpdateNotifier::AndroidUpdateNotifier(QObject *parent) : 5 | QObject(parent), 6 | _service(QtAndroid::androidService()) 7 | {} 8 | 9 | void AndroidUpdateNotifier::showNotification(bool isPositive, const QString &title, const QString &description) 10 | { 11 | _service.callMethod("showUpdateNotification", 12 | "(ZLjava/lang/String;Ljava/lang/String;)V", 13 | static_cast(isPositive), 14 | QAndroidJniObject::fromString(title).object(), 15 | QAndroidJniObject::fromString(description).object()); 16 | } 17 | 18 | void AndroidUpdateNotifier::updateProgress(int current, int max) 19 | { 20 | _service.callMethod("updateProgress", "(II)V", 21 | static_cast(current), 22 | static_cast(max)); 23 | } 24 | -------------------------------------------------------------------------------- /lib/api/infoclass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerentry.h 5 | proxerrelations.h 6 | proxernames.h 7 | 8 | info 9 | 10 | 11 | entry 12 | 13 | 14 | 15 | names 16 | 17 | 18 | 19 | relations 20 | 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /gui/core/imageloader.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGELOADER_H 2 | #define IMAGELOADER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class ImageLoader : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | Q_INVOKABLE explicit ImageLoader(QObject *parent = nullptr); 15 | 16 | public slots: 17 | void loadImage(int id); 18 | 19 | void clearCache(); 20 | 21 | signals: 22 | void imageLoaded(int id, const QImage &image); 23 | void imageLoadFailed(int id, const QString &errorString); 24 | 25 | private slots: 26 | void loadImageImpl(int id); 27 | void clearCacheImpl(); 28 | 29 | private: 30 | static const QString CacheDirName; 31 | static const QString ImageNameTemplate; 32 | 33 | QNetworkAccessManager *_nam; 34 | QCache _cache; 35 | 36 | void imageNetworkReply(int id, QNetworkReply *reply); 37 | }; 38 | 39 | #endif // IMAGELOADER_H 40 | -------------------------------------------------------------------------------- /gui/quick/qmltoast.cpp: -------------------------------------------------------------------------------- 1 | #include "qmltoast.h" 2 | #ifdef Q_OS_ANDROID 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | QmlToast::QmlToast(QObject *parent) : 9 | QObject(parent) 10 | {} 11 | 12 | void QmlToast::toast(const QString &text, QmlToast::Duration duration) 13 | { 14 | #ifdef Q_OS_ANDROID 15 | QtAndroid::runOnAndroidThread([text, duration](){ 16 | auto toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast", "makeText", 17 | "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;", 18 | QtAndroid::androidContext().object(), 19 | QAndroidJniObject::fromString(text).object(), 20 | static_cast(duration)); 21 | if(toast.isValid()) 22 | toast.callMethod("show"); 23 | }); 24 | #else 25 | Q_UNUSED(duration) 26 | qInfo().noquote() << "TOAST:" << text; 27 | #endif 28 | } 29 | -------------------------------------------------------------------------------- /gui/widgets/qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "package": "de.skycoder42.qobjectlistmodel", 5 | "provider": "qpm", 6 | "version": "1.2.1" 7 | }, 8 | { 9 | "package": "de.skycoder42.dialog-master", 10 | "provider": "qpm", 11 | "version": "1.3.2" 12 | }, 13 | { 14 | "package": "de.skycoder42.qsingleinstance", 15 | "provider": "qpm", 16 | "version": "1.2.2" 17 | }, 18 | { 19 | "package": "de.skycoder42.qtifw-advanced-setup", 20 | "provider": "qpm", 21 | "version": "2.1.1" 22 | } 23 | ], 24 | "license": { 25 | "file": "", 26 | "name": "" 27 | }, 28 | "prcFile": "", 29 | "priFile": "", 30 | "priIncludes": [ 31 | ], 32 | "publishers": { 33 | }, 34 | "source": false 35 | } 36 | -------------------------------------------------------------------------------- /gui/widgets/anirem-widgets.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/cross.ico 4 | resources/plus.ico 5 | resources/paste_plain.ico 6 | resources/page_white_copy.ico 7 | resources/arrow_refresh.ico 8 | resources/world_go.ico 9 | resources/bell_valid.ico 10 | resources/anirem_inverted.ico 11 | resources/textfield_add.ico 12 | resources/scroll_pane_list_add.ico 13 | 14 | 15 | resources/ajax-loader.gif 16 | 17 | 18 | -------------------------------------------------------------------------------- /ci/travis_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ $PLATFORM == "android_"* ]]; then 5 | # install android deps for signing 6 | sudo apt-get -qq update 7 | sudo apt-get -qq install --no-install-recommends openjdk-8-jdk unzip 8 | 9 | # android skd 10 | curl -Lo /tmp/android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip 11 | mkdir $HOME/android 12 | unzip -qq /tmp/android-sdk.zip -d $HOME/android/sdk/ 13 | rm -f /tmp/android-sdk.zip 14 | echo y | $HOME/android/sdk/tools/bin/sdkmanager --update > /dev/null 15 | echo y | $HOME/android/sdk/tools/bin/sdkmanager "build-tools;27.0.3" > /dev/null 16 | 17 | # sign 18 | mkdir -p ./install 19 | $(find "$HOME/android" -name apksigner) sign --ks "$(dirname $0)/anirem.aks" --ks-pass env:AKS_DATA --key-pass env:AKS_DATA --out "./install/anirem-${ANIREM_VER}_${PLATFORM}.apk" "build-$PLATFORM/android-build/build/outputs/apk/android-build-release-unsigned.apk" 20 | fi 21 | -------------------------------------------------------------------------------- /ci/pkgbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # run with git root as pwd 3 | # $1 out-dir 4 | # $2 version 5 | set -e 6 | 7 | currdir=$(pwd) 8 | pkgname=anirem 9 | pkgver=${1:?Invalid commands. Syntax: arch.sh } 10 | outdir=$(readlink -f ${2:?Invalid commands. Syntax: arch.sh }) 11 | pkgdir=$(mktemp -d) 12 | 13 | echo 'CONFIG += no_mobile no_updater' >> .qmake.conf 14 | ./qpminit.sh 15 | 16 | pushd $(mktemp -d) 17 | 18 | qmake -r "$currdir" 19 | make 20 | make lrelease 21 | make INSTALL_ROOT="$pkgdir" install 22 | 23 | popd 24 | 25 | install -D -m 644 -p Desktop/${pkgname}.desktop $pkgdir/usr/share/applications/${pkgname}.desktop 26 | for res in 16 22 24 32 48 64 72 96 192 256 512; do 27 | install -D -m 644 -p "icn/${pkgname}_${res}.png" "$pkgdir/usr/share/icons/hicolor/${res}x${res}/apps/${pkgname}.png" 28 | done 29 | 30 | mkdir -p "$outdir" 31 | cd "$pkgdir" 32 | export XZ_OPT=-9 33 | tar cJf "$outdir/${pkgname}-${pkgver}.tar.xz" ./* 34 | -------------------------------------------------------------------------------- /droid-service/android/src/de/skycoder42/anirem/Globals.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.os.Build; 4 | 5 | public class Globals { 6 | // alarms 7 | public static final int AlarmIntentId = 11; 8 | 9 | // preferences 10 | public static final String AutoStartPrefs = "de.skycoder42.anirem.preferences"; 11 | public static final String AutStartKey = "de.skycoder42.anirem.autostart"; 12 | 13 | // notifications channels 14 | public static final String NormalChannelId = "de.skycoder42.anirem.channel.normal"; 15 | public static final String ForegroundChannelId = "de.skycoder42.anirem.channel.foreground"; 16 | 17 | // notifications IDs 18 | public static final int ForegroundId = 21; 19 | public static final int NotifyId = 22; 20 | 21 | // colors 22 | public static final int NormalColor = 0xFF8A0E0E; // proxer red 23 | 24 | public static boolean isOreo() { 25 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gui/quick/android/src/de/skycoder42/anirem/Globals.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.os.Build; 4 | 5 | public class Globals { 6 | // alarms 7 | public static final int AlarmIntentId = 11; 8 | public static final int OpenIntentId = 12; 9 | 10 | // preferences 11 | public static final String AutoStartPrefs = "de.skycoder42.anirem.preferences"; 12 | public static final String AutStartKey = "de.skycoder42.anirem.autostart"; 13 | 14 | // notifications channels 15 | public static final String NormalChannelId = "de.skycoder42.anirem.channel.normal"; 16 | public static final String ForegroundChannelId = "de.skycoder42.anirem.channel.foreground"; 17 | 18 | // notifications IDs 19 | public static final int ForegroundId = 21; 20 | public static final int NotifyId = 22; 21 | 22 | // colors 23 | public static final int NormalColor = 0xFF8A0E0E; // proxer red 24 | 25 | public static boolean isOreo() { 26 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ci/travis_postbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ $PLATFORM == "android_"* ]]; then 5 | if [[ "$PLATFORM" == "android_armv7" ]]; then 6 | PCODE=0 7 | fi 8 | if [[ "$PLATFORM" == "android_x86" ]]; then 9 | PCODE=1 10 | fi 11 | 12 | sed -i "s/android:versionCode=\"\([[:digit:]]*\)\"/android:versionCode=\"\1%{pcode}\"/g" gui/quick/android/AndroidManifest.xml 13 | sed -i "s/%{pcode}/$PCODE/g" gui/quick/android/AndroidManifest.xml 14 | 15 | mv install build-$PLATFORM/android-build 16 | /opt/qt/$QT_VER/$PLATFORM/bin/androiddeployqt --input "build-$PLATFORM/gui/quick/android-libanirem-activity.so-deployment-settings.json" --output "build-$PLATFORM/android-build" --deployment bundled --gradle --no-gdbserver --release 17 | else 18 | if [[ $TRAVIS_OS_NAME == "linux" ]]; then 19 | apt-get -qq update 20 | apt-get -qq install libxrender1 libsecret-1-0 21 | fi 22 | 23 | rootdir=$(pwd) 24 | pushd build-$PLATFORM 25 | make INSTALL_ROOT="$rootdir/install" qtifw 26 | 27 | find "$rootdir/install" 28 | fi 29 | -------------------------------------------------------------------------------- /droid-service/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "starthelper.h" 7 | #include "androidupdatenotifier.h" 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | QAndroidService service(argc, argv); 12 | //WORKAROUND until fixed in upstream 13 | qputenv("PLUGIN_KEYSTORES_PATH", QCoreApplication::applicationDirPath().toUtf8()); 14 | qInfo() << "Overwriting keystore path to:" << qgetenv("PLUGIN_KEYSTORES_PATH"); 15 | 16 | //load translations 17 | AniRem::prepareTranslations(); 18 | 19 | QtMvvm::ServiceRegistry::instance()->registerInterface(); 20 | 21 | try { 22 | QtDataSync::Setup setup; 23 | AniRem::setup(setup, true); 24 | setup.create(); 25 | 26 | StartHelper starter; 27 | 28 | qInfo() << "service successfully started"; 29 | return service.exec(); 30 | } catch(QException &e) { 31 | qCritical() << e.what(); 32 | return EXIT_FAILURE; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gui/core/datasyncsettingsviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef DATASYNCSETTINGSVIEWMODEL_H 2 | #define DATASYNCSETTINGSVIEWMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct DataSyncSettingsEntry //copied from qsettingsgenerator 9 | { 10 | Q_GADGET 11 | 12 | public: 13 | Q_PROPERTY(QString key MEMBER key USER true) 14 | Q_PROPERTY(QByteArray value MEMBER value) 15 | 16 | QString key; 17 | QByteArray value; 18 | }; 19 | 20 | class DataSyncSettingsViewModel : public QtMvvm::SettingsViewModel 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | Q_INVOKABLE explicit DataSyncSettingsViewModel(QObject *parent = nullptr); 26 | 27 | QVariant loadValue(const QString &key, const QVariant &defaultValue) const override; 28 | void saveValue(const QString &key, const QVariant &value) override; 29 | void resetValue(const QString &key) override; 30 | 31 | private: 32 | QtDataSync::DataTypeStore *_store; 33 | }; 34 | 35 | #endif // DATASYNCSETTINGSVIEWMODEL_H 36 | -------------------------------------------------------------------------------- /lib/passiveupdater.h: -------------------------------------------------------------------------------- 1 | #ifndef PASSIVEUPDATER_H 2 | #define PASSIVEUPDATER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "lib_anirem_global.h" 8 | #include "libanirem.h" 9 | #include "seasonstatusloader.h" 10 | #include "iupdatenotifier.h" 11 | 12 | class LIB_ANIREM_EXPORT PassiveUpdater : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | QTMVVM_INJECT_PROP(SeasonStatusLoader*, loader, _loader) 17 | QTMVVM_INJECT_PROP(IUpdateNotifier*, notifier, _notifier) 18 | 19 | public: 20 | Q_INVOKABLE explicit PassiveUpdater(QObject *parent = nullptr); 21 | 22 | public slots: 23 | void performUpdateCheck(); 24 | 25 | signals: 26 | void done(bool hasNotification); 27 | 28 | private slots: 29 | void qtmvvm_init(); 30 | 31 | void completed(bool hasUpdates, const QString &errorString); 32 | 33 | private: 34 | SeasonStatusLoader *_loader; 35 | IUpdateNotifier *_notifier; 36 | QtDataSync::SyncManager *_manager; 37 | AniremStore *_store; 38 | }; 39 | 40 | #endif // PASSIVEUPDATER_H 41 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | 4 | version: build-{build} 5 | 6 | environment: 7 | QT_VER: 5.10.1 8 | PLATFORM: msvc2017_64 9 | EXTRA_MODULES: .qtremoteobjects;.skycoder42 10 | NO_TESTS: true 11 | PROXER_DATA: 12 | secure: fktz4h6RVTRjrTLC7mJCpyFsaGDk5SfT/Qu+7/1U+DOjHprCUclci8+Ph/Cx1xnHzFdekP81JSmZ3H9DBhzmFUdPWRTL/XWKshOOq7Qso+g= 13 | 14 | install: 15 | - git clone https://github.com/Skycoder42/QtModules.git .\qtmodules-travis 16 | - .\qtmodules-travis\ci\win\setup.bat 17 | 18 | build_script: 19 | - .\ci\appveyor_init.bat 20 | - .\qtmodules-travis\ci\win\build.bat 21 | - .\ci\appveyor_postbuild.bat 22 | 23 | artifacts: 24 | - path: install\repository_win.zip 25 | - path: install\Ani-Rem Installer.exe 26 | 27 | deploy: 28 | provider: GitHub 29 | auth_token: 30 | secure: Cp5GRQku2ZWnKPE12NB5q11ZO0Fr5mlzdUTjnLpYJr/dki4LPVqm231edFggogy8 31 | artifact: /.*/ 32 | force_update: false 33 | on: 34 | appveyor_repo_tag: true 35 | 36 | cache: 37 | - 'C:\Users\appveyor\AppData\Local\Skycoder42\qpmx\cache -> appveyor.yml' 38 | -------------------------------------------------------------------------------- /gui/widgets/widgetsupdatenotifier.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETSUPDATENOTIFIER_H 2 | #define WIDGETSUPDATENOTIFIER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class WidgetsUpdateNotifier : public QObject, public IUpdateNotifier 12 | { 13 | Q_OBJECT 14 | Q_INTERFACES(IUpdateNotifier) 15 | 16 | public: 17 | Q_INVOKABLE explicit WidgetsUpdateNotifier(QObject *parent = nullptr); 18 | 19 | public slots: 20 | void showNotification(bool isPositive, const QString &title, const QString &description) override; 21 | void updateProgress(int value, int max) override; 22 | 23 | private slots: 24 | void timerTriggered(); 25 | void trayActivated(QSystemTrayIcon::ActivationReason reason); 26 | void showMainViewModel(); 27 | void quit(); 28 | 29 | private: 30 | QSystemTrayIcon *_trayIcon; 31 | QScopedPointer _trayMenu; 32 | 33 | QTimer *_blinkTimer; 34 | QIcon _normalIcon; 35 | QIcon _invertIcon; 36 | bool _currentNormal; 37 | }; 38 | 39 | #endif // WIDGETSUPDATENOTIFIER_H 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | # qpmx 75 | .qpmx-dev-cache 76 | *.tsdummy.cpp 77 | *.json.user 78 | 79 | .old 80 | /proxer-api-key.h 81 | -------------------------------------------------------------------------------- /gui/quick/proxerimageprovider.h: -------------------------------------------------------------------------------- 1 | #ifndef PROXERIMAGEPROVIDER_H 2 | #define PROXERIMAGEPROVIDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class ProxerImageResponse : public QQuickImageResponse 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | ProxerImageResponse(int _id, const QSize &_requestedSize); 15 | 16 | QQuickTextureFactory *textureFactory() const override; 17 | QString errorString() const override; 18 | 19 | public slots: 20 | void cancel() override; 21 | 22 | void imageLoaded(int _id, const QImage &image); 23 | void imageLoadFailed(int _id, const QString &errorString); 24 | 25 | private: 26 | int _id; 27 | QSize _requestedSize; 28 | QImage _result; 29 | QString _error; 30 | }; 31 | 32 | class ProxerImageProvider : public QQuickAsyncImageProvider 33 | { 34 | public: 35 | ProxerImageProvider(); 36 | 37 | ImageType imageType() const override; 38 | Flags flags() const override; 39 | 40 | QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; 41 | 42 | private: 43 | ImageLoader *_loader; 44 | }; 45 | 46 | #endif // PROXERIMAGEPROVIDER_H 47 | -------------------------------------------------------------------------------- /gui/widgets/addanimedialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDANIMEDIALOG_H 2 | #define ADDANIMEDIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class AddAnimeDialog; 11 | } 12 | 13 | class AddAnimeDialog : public QDialog 14 | { 15 | Q_OBJECT 16 | 17 | Q_PROPERTY(int uiId READ uiId WRITE setUiId NOTIFY uiIdChanged) 18 | 19 | public: 20 | Q_INVOKABLE AddAnimeDialog(QtMvvm::ViewModel *viewModel, QWidget *parent = nullptr); 21 | ~AddAnimeDialog() override; 22 | 23 | void accept() override; 24 | 25 | signals: 26 | void uiIdChanged(); 27 | 28 | private slots: 29 | void setUiId(int id); 30 | void loadingChanged(bool loading); 31 | 32 | void imageLoaded(int id, const QImage &image); 33 | void imageLoadFailed(int id, const QString &error); 34 | 35 | private: 36 | enum PmState { 37 | PmCleared, 38 | PmLoading, 39 | PmLoaded, 40 | PmError 41 | }; 42 | 43 | AddAnimeViewModel *_viewModel; 44 | Ui::AddAnimeDialog *_ui; 45 | 46 | QMovie *_loadingMovie; 47 | QPixmap _currentPixmap; 48 | PmState _pmState; 49 | 50 | void updatePm(); 51 | 52 | int uiId() const; 53 | }; 54 | 55 | #endif // ADDANIMEDIALOG_H 56 | -------------------------------------------------------------------------------- /gui/quick/AnimeHistoryDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Controls.Material 2.3 4 | import QtQuick.Controls.Universal 2.3 5 | import QtQuick.Layouts 1.3 6 | import de.skycoder42.QtMvvm.Quick 1.0 7 | 8 | ItemDelegate { 9 | id: delegate 10 | width: parent.width 11 | text: name 12 | 13 | contentItem: RowLayout { 14 | Image { 15 | id: previewImage 16 | asynchronous: true 17 | fillMode: Image.PreserveAspectFit 18 | horizontalAlignment: Image.AlignHCenter 19 | verticalAlignment: Image.AlignVCenter 20 | Layout.minimumWidth: 42 21 | Layout.maximumWidth: 42 22 | Layout.minimumHeight: 42 23 | Layout.maximumHeight: 42 24 | source: "image://proxer/%1".arg(id) 25 | mipmap: true 26 | } 27 | 28 | Label { 29 | id: titleLabel 30 | elide: Label.ElideRight 31 | horizontalAlignment: Qt.AlignLeft 32 | verticalAlignment: Qt.AlignVCenter 33 | Layout.fillWidth: true 34 | Layout.fillHeight: true 35 | text: delegate.text 36 | } 37 | 38 | Label { 39 | id: ratingLabel 40 | Layout.fillHeight: true 41 | horizontalAlignment: Qt.AlignRight 42 | verticalAlignment: Qt.AlignVCenter 43 | text: qsTr("%1/10 ☆").arg(rating) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/api/userclass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib_anirem_global.h 4 | proxerlist.h 5 | proxerlogin.h 6 | 7 | user 8 | 9 | 10 | list 11 | 12 | false 13 | 0 14 | 100 15 | changeDateDESC 16 | stateFilter0 17 | 18 | 19 | login 20 | 21 | 22 | 23 | 24 | login 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /gui/core/aniremsettingsviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "aniremsettingsviewmodel.h" 2 | #include "aniremapp.h" 3 | #include 4 | 5 | AniremSettingsViewModel::AniremSettingsViewModel(QObject *parent) : 6 | DataSyncSettingsViewModel(parent), 7 | _loader(nullptr) 8 | {} 9 | 10 | void AniremSettingsViewModel::saveValue(const QString &key, const QVariant &value) 11 | { 12 | DataSyncSettingsViewModel::saveValue(key, value); 13 | if(key == QStringLiteral("updates/autoCheck")) 14 | coreApp->updateAutoStartState(value.toInt()); 15 | } 16 | 17 | void AniremSettingsViewModel::resetValue(const QString &key) 18 | { 19 | DataSyncSettingsViewModel::resetValue(key); 20 | if(key == QStringLiteral("updates/autoCheck")) 21 | coreApp->updateAutoStartState(7); 22 | } 23 | 24 | void AniremSettingsViewModel::callAction(const QString &key, const QVariantMap ¶meters) 25 | { 26 | Q_UNUSED(parameters) 27 | if(key == QStringLiteral("clearcache")) { 28 | QtMvvm::question(tr("Clear Cache?"), 29 | tr("Do you want to remove all cached preview images? This will free some storage, " 30 | "but the app will have to download previews of you animes again as soon as they are needed."), 31 | this, [this](bool ok){ 32 | if(ok) 33 | _loader->clearCache(); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gui/widgets/entrydialog.cpp: -------------------------------------------------------------------------------- 1 | #include "entrydialog.h" 2 | #include "ui_entrydialog.h" 3 | #include 4 | #include 5 | 6 | EntryDialog::EntryDialog(QtMvvm::ViewModel *viewModel, QWidget *parent) : 7 | QDialog(parent), 8 | _viewModel(static_cast(viewModel)), 9 | _ui(new Ui::EntryDialog) 10 | { 11 | _ui->setupUi(this); 12 | DialogMaster::masterDialog(this); 13 | 14 | _ui->typeComboBox->addItems(_viewModel->listTypes()); 15 | QtMvvm::bind(_viewModel, "typeIndex", 16 | _ui->typeComboBox, "currentIndex"); 17 | 18 | connect(_viewModel, &EntryViewModel::modelChanged, 19 | this, [this](ProxerEntryModel *model){ 20 | _ui->treeView->setModel(model); 21 | 22 | _ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); 23 | _ui->treeView->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); 24 | _ui->treeView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); 25 | _ui->treeView->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); 26 | }); 27 | } 28 | 29 | EntryDialog::~EntryDialog() 30 | { 31 | delete _ui; 32 | } 33 | 34 | void EntryDialog::on_treeView_activated(const QModelIndex &index) 35 | { 36 | if(index.isValid()) 37 | _viewModel->addAnime(_viewModel->model()->getId(index)); 38 | } 39 | -------------------------------------------------------------------------------- /lib/seasonstatusloader.h: -------------------------------------------------------------------------------- 1 | #ifndef SEASONSTATUSLOADER_H 2 | #define SEASONSTATUSLOADER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "lib_anirem_global.h" 8 | #include "libanirem.h" 9 | #include "infoclass.h" 10 | #include "syncedsettings.h" 11 | 12 | class LIB_ANIREM_EXPORT SeasonStatusLoader : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | QTMVVM_INJECT_PROP(SyncedSettings*, settings, _settings) 17 | 18 | public: 19 | Q_INVOKABLE explicit SeasonStatusLoader(QObject *parent = nullptr); 20 | 21 | bool isUpdating() const; 22 | void preClean(); 23 | 24 | public slots: 25 | void checkForUpdates(bool useInterval = false); 26 | void checkForUpdates(const AnimeInfo &info); 27 | 28 | signals: 29 | void started(); 30 | void updated(int value, int max); 31 | void completed(bool hasUpdates, const QString &errorString = {}); 32 | 33 | private slots: 34 | void checkNext(); 35 | void error(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType); 36 | 37 | private: 38 | AniremStore *_store; 39 | InfoClass *_infoClass; 40 | SyncedSettings *_settings; 41 | 42 | QQueue _updateQueue; 43 | int _lastMax; 44 | bool _anyUpdated; 45 | 46 | void addInfos(const QList &infos); 47 | }; 48 | 49 | #endif // SEASONSTATUSLOADER_H 50 | -------------------------------------------------------------------------------- /lib/apihelper.cpp: -------------------------------------------------------------------------------- 1 | #include "apihelper.h" 2 | #include 3 | #include 4 | #include "libanirem.h" 5 | using namespace QtRestClient; 6 | 7 | QString ApiHelper::transformError(ProxerStatus status, int) 8 | { 9 | return status.message(); 10 | } 11 | 12 | QString ApiHelper::formatError(const QString &error, int errorCode, RestReply::ErrorType type) 13 | { 14 | qWarning() << "API-Error type" 15 | << type 16 | << "code" 17 | << errorCode 18 | << "with error string" 19 | << error; 20 | 21 | switch (type) { 22 | case RestReply::NetworkError: 23 | return tr("Network Error: %1").arg(error); 24 | case RestReply::JsonParseError: 25 | return tr("Data Error: %1").arg(error); 26 | case RestReply::FailureError: 27 | if(errorCode == 1005) { 28 | AniRem::setProxerToken({}, LocalSettings::instance()); 29 | return tr("Invalid Login-Token! The old token has been removed. Please try again."); 30 | } else 31 | return tr("Proxer-API Error: %1").arg(error); 32 | case RestReply::DeserializationError: 33 | return tr("Data Error: %1").arg(error); 34 | default: 35 | Q_UNREACHABLE(); 36 | return QString(); 37 | } 38 | } 39 | 40 | bool ApiHelper::testValid(int code, ProxerStatus status) 41 | { 42 | return status.error() == 0 && (code / 100) == 2; 43 | } 44 | -------------------------------------------------------------------------------- /gui/core/detailsviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef DETAILSVIEWMODEL_H 2 | #define DETAILSVIEWMODEL_H 3 | 4 | #include 5 | 6 | #include 7 | #include "imageloader.h" 8 | 9 | class DetailsViewModel : public QtMvvm::ViewModel 10 | { 11 | Q_OBJECT 12 | 13 | Q_PROPERTY(AnimeInfo animeInfo READ animeInfo WRITE setAnimeInfo NOTIFY animeInfoChanged) 14 | Q_PROPERTY(QString detailsText READ detailsText NOTIFY animeInfoChanged) 15 | 16 | QTMVVM_INJECT_PROP(ImageLoader*, imageLoader, _loader) 17 | 18 | public: 19 | static const QString ParamInfo; 20 | static const QString ParamOwner; 21 | 22 | static QVariantHash params(const AnimeInfo &info, QtMvvm::ViewModel *owner = nullptr); 23 | 24 | Q_INVOKABLE explicit DetailsViewModel(QObject *parent = nullptr); 25 | 26 | AnimeInfo animeInfo() const; 27 | QString detailsText() const; 28 | 29 | ImageLoader* imageLoader() const; 30 | 31 | public slots: 32 | void setAnimeInfo(const AnimeInfo &animeInfo); 33 | void clear(); 34 | 35 | void uncheckAnime(); 36 | 37 | signals: 38 | void animeInfoChanged(); 39 | 40 | private slots: 41 | void dataChanged(const QString &key, const QVariant &value); 42 | 43 | protected: 44 | void onInit(const QVariantHash ¶ms) override; 45 | 46 | private: 47 | AniremStore *_store; 48 | ImageLoader *_loader; 49 | 50 | AnimeInfo _animeInfo; 51 | }; 52 | 53 | #endif // DETAILSVIEWMODEL_H 54 | -------------------------------------------------------------------------------- /gui/widgets/logindialog.cpp: -------------------------------------------------------------------------------- 1 | #include "logindialog.h" 2 | #include "ui_logindialog.h" 3 | #include 4 | #include 5 | 6 | LoginDialog::LoginDialog(QtMvvm::ViewModel *viewModel, QWidget *parent) : 7 | QDialog(parent), 8 | _viewModel(static_cast(viewModel)), 9 | _ui(new Ui::LoginDialog) 10 | { 11 | _ui->setupUi(this); 12 | DialogMaster::masterDialog(this, true); 13 | 14 | QtMvvm::bind(_viewModel, "userName", 15 | _ui->usernameLineEdit, "text", 16 | QtMvvm::Binding::TwoWay, 17 | nullptr, 18 | "editingFinished()"); 19 | QtMvvm::bind(_viewModel, "password", 20 | _ui->passwordLineEdit, "text", 21 | QtMvvm::Binding::TwoWay, 22 | nullptr, 23 | "editingFinished()"); 24 | QtMvvm::bind(_viewModel, "authCode", 25 | _ui->faCodeLineEdit, "text", 26 | QtMvvm::Binding::TwoWay, 27 | nullptr, 28 | "editingFinished()"); 29 | QtMvvm::bind(_viewModel, "storePassword", 30 | _ui->rememberPasswordCheckBox, "checked"); 31 | 32 | connect(_viewModel, &LoginViewModel::loadingChanged, 33 | this, &LoginDialog::setDisabled); 34 | connect(_viewModel, &LoginViewModel::loginSuccessful, 35 | this, &LoginDialog::close); 36 | 37 | //FEATURE disable for now 38 | _ui->rememberPasswordCheckBox->hide(); 39 | } 40 | 41 | LoginDialog::~LoginDialog() 42 | { 43 | delete _ui; 44 | } 45 | 46 | void LoginDialog::accept() 47 | { 48 | _viewModel->login(); 49 | } 50 | -------------------------------------------------------------------------------- /gui/widgets/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "animemodel.h" 12 | 13 | namespace Ui { 14 | class MainWindow; 15 | } 16 | 17 | class MainWindow : public QMainWindow 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | Q_INVOKABLE MainWindow(QtMvvm::ViewModel *viewModel, QWidget *parent = nullptr); 23 | ~MainWindow() override; 24 | 25 | public slots: 26 | void showStatus(const QString &message); 27 | void setProgress(int value, int max); 28 | 29 | void updateLoadStatus(bool isLoading); 30 | 31 | void addDockAction(QAction *action); 32 | 33 | private slots: 34 | void updatePreview(const QModelIndex &index); 35 | void updateCount(); 36 | 37 | void on_actionRemove_Anime_triggered(); 38 | void on_actionCopy_selected_Info_triggered(); 39 | void on_action_Open_Anime_in_browser_triggered(); 40 | void on_action_Unmark_new_seasons_triggered(); 41 | void on_seasonTreeView_activated(const QModelIndex &index); 42 | 43 | private: 44 | MainViewModel *_viewModel; 45 | Ui::MainWindow *_ui; 46 | 47 | QLabel *_statusLabel; 48 | QProgressBar *_statusProgress; 49 | 50 | AnimeModel *_animeModel; 51 | QSortFilterProxyModel *_proxyModel; 52 | 53 | QModelIndex mapToModel(const QModelIndex &uiIndex) const; 54 | int infoId(const QModelIndex &uiIndex) const; 55 | }; 56 | 57 | #endif // MAINWINDOW_H 58 | -------------------------------------------------------------------------------- /gui/core/entryviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTRYVIEWMODEL_H 2 | #define ENTRYVIEWMODEL_H 3 | 4 | #include 5 | #include 6 | 7 | class EntryViewModel : public QtMvvm::ViewModel 8 | { 9 | Q_OBJECT 10 | 11 | Q_PROPERTY(ProxerEntryModel* model READ model NOTIFY modelChanged) 12 | Q_PROPERTY(int typeIndex READ typeIndex WRITE setTypeIndex NOTIFY typeIndexChanged) 13 | Q_PROPERTY(QStringList listTypes READ listTypes CONSTANT) 14 | 15 | QTMVVM_INJECT_PROP(SyncedSettings*, settings, _settings) 16 | 17 | public: 18 | 19 | Q_INVOKABLE explicit EntryViewModel(QObject *parent = nullptr); 20 | 21 | ProxerEntryModel *model() const; 22 | int typeIndex() const; 23 | QStringList listTypes() const; 24 | 25 | public slots: 26 | void addAnime(int id); 27 | 28 | void setTypeIndex(int typeIndex); 29 | 30 | signals: 31 | void modelChanged(ProxerEntryModel *model); 32 | void typeIndexChanged(int typeIndex); 33 | 34 | protected: 35 | void onInit(const QVariantHash ¶ms) override; 36 | void onResult(quint32 requestCode, const QVariant &result) override; 37 | 38 | private slots: 39 | void loginNeeded(); 40 | void apiError(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType); 41 | 42 | private: 43 | static const int LoginRequestCode = 0x4201; 44 | static const int AddRequestCode = 0x4202; 45 | 46 | SyncedSettings *_settings; 47 | ProxerEntryModel *_model; 48 | bool _showingChild; 49 | }; 50 | 51 | #endif // ENTRYVIEWMODEL_H 52 | -------------------------------------------------------------------------------- /droid-service/starthelper.cpp: -------------------------------------------------------------------------------- 1 | #include "starthelper.h" 2 | #include 3 | #include 4 | #include 5 | 6 | QMutex StartHelper::_runMutex; 7 | QPointer StartHelper::_runInstance = nullptr; 8 | bool StartHelper::_wasStarted = false; 9 | 10 | StartHelper::StartHelper(QObject *parent) : 11 | QObject(parent) 12 | { 13 | QMutexLocker locker(&_runMutex); 14 | if(_wasStarted) 15 | QMetaObject::invokeMethod(_runInstance, "startUpdater", Qt::QueuedConnection); 16 | else 17 | _runInstance = this; 18 | } 19 | 20 | void StartHelper::handleStart() 21 | { 22 | QMutexLocker locker(&_runMutex); 23 | _wasStarted = true; 24 | if(_runInstance) { 25 | QMetaObject::invokeMethod(_runInstance, "startUpdater", Qt::QueuedConnection); 26 | _runInstance = nullptr;//set to null again, this way only 1 queued invokation is done 27 | } 28 | } 29 | 30 | void StartHelper::startUpdater() 31 | { 32 | auto updater = QtMvvm::ServiceRegistry::instance()->constructInjected(); 33 | connect(updater, &PassiveUpdater::done, 34 | this, &StartHelper::done); 35 | updater->performUpdateCheck(); 36 | } 37 | 38 | void StartHelper::done() 39 | { 40 | auto service = QtAndroid::androidService(); 41 | service.callMethod("completeAction"); 42 | } 43 | 44 | extern "C" { 45 | 46 | JNIEXPORT void JNICALL Java_de_skycoder42_anirem_AniremService_handleStart(JNIEnv */*env*/, jobject /*obj*/) 47 | { 48 | StartHelper::handleStart(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /gui/core/core.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT += mvvmcore mvvmdatasynccore concurrent 4 | android: QT += androidextras 5 | CONFIG += static 6 | win32: CONFIG += skip_target_version_ext 7 | 8 | TARGET = anirem-core 9 | 10 | HEADERS += \ 11 | mainviewmodel.h \ 12 | aniremapp.h \ 13 | datasyncsettingsviewmodel.h \ 14 | addanimeviewmodel.h \ 15 | imageloader.h \ 16 | detailsviewmodel.h \ 17 | aniremsettingsviewmodel.h \ 18 | entryviewmodel.h \ 19 | proxerentrymodel.h \ 20 | loginviewmodel.h 21 | 22 | SOURCES += \ 23 | mainviewmodel.cpp \ 24 | aniremapp.cpp \ 25 | datasyncsettingsviewmodel.cpp \ 26 | addanimeviewmodel.cpp \ 27 | imageloader.cpp \ 28 | detailsviewmodel.cpp \ 29 | aniremsettingsviewmodel.cpp \ 30 | entryviewmodel.cpp \ 31 | proxerentrymodel.cpp \ 32 | loginviewmodel.cpp 33 | 34 | RESOURCES += \ 35 | anirem-core.qrc 36 | 37 | TRANSLATIONS += anirem_core_de.ts 38 | 39 | DISTFILES += $$TRANSLATIONS 40 | QTMVVM_TS_SETTINGS = settings.xml 41 | _never_true_condition: SOURCES += $$files($$PWD/.ts-dummy/*) 42 | PRE_TARGETDEPS += qtmvvm-tsgen 43 | 44 | # install 45 | qpmx_ts_target.path = $$INSTALL_TRANSLATIONS 46 | INSTALLS += qpmx_ts_target 47 | 48 | include(../../lib.pri) 49 | 50 | !ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) 51 | else: include($$OUT_PWD/qpmx_generated.pri) 52 | 53 | QMAKE_EXTRA_TARGETS += qtifw 54 | -------------------------------------------------------------------------------- /gui/quick/EntryView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Layouts 1.3 4 | import de.skycoder42.QtMvvm.Core 1.0 5 | import de.skycoder42.QtMvvm.Quick 1.0 6 | import de.skycoder42.anirem 1.0 7 | import "../../qml" 8 | 9 | Page { 10 | id: mainView 11 | property EntryViewModel viewModel: null 12 | 13 | header: ContrastToolBar { 14 | id: toolbar 15 | 16 | RowLayout { 17 | anchors.fill: parent 18 | spacing: 0 19 | 20 | ToolBarLabel { 21 | id: titleLabel 22 | Layout.fillWidth: true 23 | text: qsTr("%1 History").arg(viewModel.listTypes[viewModel.typeIndex]) 24 | } 25 | 26 | MenuButton { 27 | id: menuButton 28 | icon.name: "view-filter" 29 | icon.source: "qrc:/icons/ic_filter_list.svg" 30 | text: qsTr("Select Mode") 31 | 32 | ButtonGroup { 33 | id: indexGroup 34 | exclusive: true 35 | } 36 | 37 | Repeater { 38 | id: typeRep 39 | model: viewModel.listTypes 40 | delegate: MenuItem { 41 | id: mItem 42 | text: modelData 43 | checkable: true 44 | checked: viewModel.typeIndex == index 45 | onClicked: viewModel.typeIndex = index 46 | 47 | Component.onCompleted: indexGroup.addButton(mItem) 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | PresenterProgress {} 55 | 56 | ScrollView { 57 | anchors.fill: parent 58 | clip: true 59 | 60 | ListView { 61 | model: viewModel.model 62 | 63 | delegate: AnimeHistoryDelegate { 64 | onClicked: viewModel.addAnime(id) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /gui/core/datasyncsettingsviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "datasyncsettingsviewmodel.h" 2 | #include 3 | #include 4 | 5 | DataSyncSettingsViewModel::DataSyncSettingsViewModel(QObject *parent) : 6 | SettingsViewModel(parent), 7 | _store(new QtDataSync::DataTypeStore(this)) 8 | {} 9 | 10 | QVariant DataSyncSettingsViewModel::loadValue(const QString &key, const QVariant &defaultValue) const 11 | { 12 | try { 13 | QVariant value; 14 | { 15 | auto data = _store->load(key).value; 16 | QDataStream stream(data); 17 | stream >> value; 18 | } 19 | return value; 20 | } catch (QtDataSync::NoDataException &e) { 21 | return defaultValue; 22 | } catch (QException &e) { 23 | qCritical() << "Failed to load entry" << key << "from datasync settings with error:" 24 | << e.what(); 25 | return defaultValue; 26 | } 27 | } 28 | 29 | void DataSyncSettingsViewModel::saveValue(const QString &key, const QVariant &value) 30 | { 31 | try { 32 | QByteArray data; 33 | { 34 | QDataStream stream(&data, QIODevice::WriteOnly); 35 | stream << value; 36 | } 37 | _store->save({key, data}); 38 | } catch (QException &e) { 39 | qCritical() << "Failed to save entry" << key << "to datasync settings with error:" 40 | << e.what(); 41 | } 42 | } 43 | 44 | void DataSyncSettingsViewModel::resetValue(const QString &key) 45 | { 46 | try { 47 | _store->remove(key); 48 | } catch (QException &e) { 49 | qCritical() << "Failed to remove entry" << key << "from datasync settings with error:" 50 | << e.what(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /droid-service/android/src/de/skycoder42/anirem/AniremService.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.content.Intent; 4 | 5 | import android.graphics.BitmapFactory; 6 | 7 | import android.support.v4.app.NotificationCompat; 8 | 9 | import org.qtproject.qt5.android.bindings.QtService; 10 | 11 | public class AniremService extends QtService { 12 | @Override 13 | public void onDestroy() { 14 | super.onDestroy(); 15 | // explicitly exit to prevent the process from beeing cached 16 | System.exit(0); 17 | } 18 | 19 | @Override 20 | public int onStartCommand(Intent intent, int flags, int startId) { 21 | int result = super.onStartCommand(intent, flags, startId); 22 | 23 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Globals.ForegroundChannelId) 24 | .setContentTitle(getString(R.string.update_progress_title)) 25 | .setContentText(getString(R.string.update_progress_text)) 26 | .setContentInfo(getString(R.string.app_name)) 27 | .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) 28 | .setSmallIcon(R.drawable.ic_notification) 29 | .setColor(Globals.NormalColor) 30 | .setOngoing(true) 31 | .setProgress(0, 0, true) 32 | .setCategory(NotificationCompat.CATEGORY_PROGRESS); 33 | 34 | if (!Globals.isOreo()) 35 | builder.setPriority(NotificationCompat.PRIORITY_MIN); 36 | 37 | startForeground(Globals.ForegroundId, builder.build()); 38 | return result; 39 | } 40 | 41 | public void completeAction() { 42 | stopForeground(true); 43 | stopService(new Intent(this, AniremService.class));//Stop myself 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /droid-service/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.2.3' 8 | } 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | jcenter() 14 | maven { 15 | url "https://maven.google.com" 16 | } 17 | } 18 | } 19 | 20 | apply plugin: 'com.android.application' 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile "com.android.support:support-compat:27.1.0" 25 | } 26 | 27 | android { 28 | /******************************************************* 29 | * The following variables: 30 | * - androidBuildToolsVersion, 31 | * - androidCompileSdkVersion 32 | * - qt5AndroidDir - holds the path to qt android files 33 | * needed to build any Qt application 34 | * on Android. 35 | * 36 | * are defined in gradle.properties file. This file is 37 | * updated by QtCreator and androiddeployqt tools. 38 | * Changing them manually might break the compilation! 39 | *******************************************************/ 40 | 41 | compileSdkVersion androidCompileSdkVersion.toInteger() 42 | 43 | buildToolsVersion androidBuildToolsVersion 44 | 45 | sourceSets { 46 | main { 47 | manifest.srcFile 'AndroidManifest.xml' 48 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] 49 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] 50 | res.srcDirs = [qt5AndroidDir + '/res', 'res'] 51 | resources.srcDirs = ['src'] 52 | renderscript.srcDirs = ['src'] 53 | assets.srcDirs = ['assets'] 54 | jniLibs.srcDirs = ['libs'] 55 | } 56 | } 57 | 58 | lintOptions { 59 | abortOnError false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gui/quick/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.2.3' 8 | } 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | jcenter() 14 | maven { 15 | url "https://maven.google.com" 16 | } 17 | } 18 | } 19 | 20 | apply plugin: 'com.android.application' 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile "com.android.support:support-compat:27.1.0" 25 | } 26 | 27 | android { 28 | /******************************************************* 29 | * The following variables: 30 | * - androidBuildToolsVersion, 31 | * - androidCompileSdkVersion 32 | * - qt5AndroidDir - holds the path to qt android files 33 | * needed to build any Qt application 34 | * on Android. 35 | * 36 | * are defined in gradle.properties file. This file is 37 | * updated by QtCreator and androiddeployqt tools. 38 | * Changing them manually might break the compilation! 39 | *******************************************************/ 40 | 41 | compileSdkVersion androidCompileSdkVersion.toInteger() 42 | 43 | buildToolsVersion androidBuildToolsVersion 44 | 45 | sourceSets { 46 | main { 47 | manifest.srcFile 'AndroidManifest.xml' 48 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] 49 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] 50 | res.srcDirs = [qt5AndroidDir + '/res', 'res'] 51 | resources.srcDirs = ['src'] 52 | renderscript.srcDirs = ['src'] 53 | assets.srcDirs = ['assets'] 54 | jniLibs.srcDirs = ['libs'] 55 | } 56 | } 57 | 58 | lintOptions { 59 | abortOnError false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gui/quick/anirem-quick.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qtquickcontrols2.conf 4 | qtquickcontrols2_android.conf 5 | qtquickcontrols2_win.conf 6 | 7 | 8 | App.qml 9 | AnimeInfoDelegate.qml 10 | AnimeHistoryDelegate.qml 11 | SubButton.qml 12 | 13 | 14 | MainView.qml 15 | AddAnimeDialog.qml 16 | DetailsView.qml 17 | EntryView.qml 18 | LoginDialog.qml 19 | 20 | 21 | icons/ic_add_white_24px.svg 22 | icons/ic_delete_forever_white_24px.svg 23 | icons/ic_autorenew_white_24px.svg 24 | icons/ic_check_white_24px.svg 25 | icons/ic_open_in_browser_white_24px.svg 26 | icons/ic_arrow_back_white_24px.svg 27 | icons/ic_text_fields_white_24px.svg 28 | icons/ic_playlist_add_white_24px.svg 29 | icons/ic_content_paste_white_24px.svg 30 | icons/ic_filter_list_white_24px.svg 31 | 32 | 33 | -------------------------------------------------------------------------------- /gui/quick/SubButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import de.skycoder42.QtMvvm.Quick 1.0 4 | 5 | RoundActionButton { 6 | id: _subButton 7 | 8 | property Item reference: parent 9 | property real btnSpacing: 16 10 | property bool expanded: false 11 | 12 | highlighted: false 13 | anchors.horizontalCenter: reference.horizontalCenter 14 | anchors.verticalCenter: reference.verticalCenter 15 | implicitHeight: 40 + padding 16 | implicitWidth: 40 + padding 17 | state: expanded ? "expanded" : "collapsed" 18 | 19 | states: [ 20 | State { 21 | name: "collapsed" 22 | PropertyChanges { 23 | target: _subButton 24 | anchors.verticalCenterOffset: 0 25 | visible: false 26 | } 27 | }, 28 | State { 29 | name: "expanded" 30 | PropertyChanges { 31 | target: _subButton 32 | anchors.verticalCenterOffset: -1 * (reference.height/2 + _subButton.height/2 + btnSpacing) 33 | visible: true 34 | } 35 | } 36 | ] 37 | 38 | transitions: [ 39 | Transition { 40 | from: "collapsed" 41 | to: "expanded" 42 | SequentialAnimation { 43 | PropertyAnimation { 44 | target: _subButton 45 | property: "visible" 46 | duration: 0 47 | } 48 | PropertyAnimation { 49 | target: _subButton 50 | property: "anchors.verticalCenterOffset" 51 | duration: 250 52 | easing.type: Easing.OutCubic 53 | } 54 | } 55 | }, 56 | Transition { 57 | from: "expanded" 58 | to: "collapsed" 59 | SequentialAnimation { 60 | PropertyAnimation { 61 | target: _subButton 62 | property: "anchors.verticalCenterOffset" 63 | duration: 250 64 | easing.type: Easing.InCubic 65 | } 66 | PropertyAnimation { 67 | target: _subButton 68 | property: "visible" 69 | duration: 0 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /gui/widgets/animemodel.cpp: -------------------------------------------------------------------------------- 1 | #include "animemodel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | AnimeModel::AnimeModel(QtDataSync::DataStoreModel *srcModel, QObject *parent) : 8 | QObjectProxyModel({tr("Id"), tr("Name"), tr("Season Count"), tr("Last updated"), tr("Season Overview")}, parent) 9 | { 10 | setSourceModel(srcModel); 11 | 12 | addMapping(0, Qt::DisplayRole, "id"); 13 | addMapping(0, Qt::ToolTipRole, "id"); 14 | 15 | addMapping(1, Qt::DisplayRole, "title"); 16 | addMapping(1, Qt::ToolTipRole, "title"); 17 | 18 | addMapping(2, Qt::DisplayRole, "totalSeasonCount"); 19 | addMapping(2, Qt::ToolTipRole, "totalSeasonCount"); 20 | 21 | addMapping(3, Qt::DisplayRole, "lastUpdateCheck"); 22 | addMapping(3, Qt::ToolTipRole, "lastUpdateCheck"); 23 | 24 | addMapping(4, Qt::DisplayRole, "relationsUrl"); 25 | addMapping(4, Qt::ToolTipRole, "relationsUrl"); 26 | } 27 | 28 | QVariant AnimeModel::data(const QModelIndex &index, int role) const 29 | { 30 | auto src = mapToSource(index); 31 | if(!src.isValid()) 32 | return {}; 33 | 34 | auto info = static_cast(sourceModel())->object(src); 35 | switch (role) { 36 | case Qt::DisplayRole: 37 | case Qt::ToolTipRole: 38 | switch(index.column()) { 39 | case 2: 40 | return QLocale().toString(info.totalSeasonCount()); 41 | case 3: 42 | return info.lastUpdateCheck().toString(Qt::DefaultLocaleShortDate); 43 | default: 44 | break; 45 | } 46 | break; 47 | case Qt::FontRole: 48 | if(info.hasNewSeasons()) { 49 | QFont font; 50 | font.setBold(true); 51 | return font; 52 | } else 53 | break; 54 | case Qt::ForegroundRole: 55 | if(info.hasNewSeasons()) 56 | return QColor(0x8A, 0x0E, 0x0E); 57 | else 58 | break; 59 | default: 60 | break; 61 | } 62 | 63 | return QObjectProxyModel::data(index, role); 64 | } 65 | -------------------------------------------------------------------------------- /lib/jsonseasondataconverter.cpp: -------------------------------------------------------------------------------- 1 | #include "animeinfo.h" 2 | #include "jsonseasondataconverter.h" 3 | 4 | #include 5 | 6 | bool JsonSeasonDataConverter::canConvert(int metaTypeId) const 7 | { 8 | return metaTypeId == qMetaTypeId(); 9 | } 10 | 11 | QList JsonSeasonDataConverter::jsonTypes() const 12 | { 13 | return {QJsonValue::Object}; 14 | } 15 | 16 | QJsonValue JsonSeasonDataConverter::serialize(int propertyType, const QVariant &value, const QJsonTypeConverter::SerializationHelper *helper) const 17 | { 18 | Q_UNUSED(propertyType) 19 | Q_UNUSED(helper) 20 | 21 | auto v = value.value(); 22 | QJsonObject o; 23 | auto metaEnum = QMetaEnum::fromType(); 24 | for(const auto &key : v.keys()) { 25 | QJsonObject pair; 26 | pair[QStringLiteral("count")] = v.value(key).first; 27 | pair[QStringLiteral("changed")] = v.value(key).second; 28 | o.insert(QString::fromUtf8(metaEnum.valueToKey(key)), pair); 29 | } 30 | return o; 31 | } 32 | 33 | QVariant JsonSeasonDataConverter::deserialize(int propertyType, const QJsonValue &value, QObject *parent, const QJsonTypeConverter::SerializationHelper *helper) const 34 | { 35 | Q_UNUSED(propertyType) 36 | Q_UNUSED(parent) 37 | Q_UNUSED(helper) 38 | 39 | auto o = value.toObject(); 40 | AnimeInfo::SeasonMap v; 41 | auto metaEnum = QMetaEnum::fromType(); 42 | for(const auto &key : o.keys()) { 43 | auto pair = o.value(key).toObject(); 44 | AnimeInfo::SeasonInfo info; 45 | if(pair.contains(QStringLiteral("first"))) { 46 | info.first = pair[QStringLiteral("first")].toInt(); 47 | info.second = pair[QStringLiteral("second")].toBool(); 48 | } else { 49 | info.first = pair[QStringLiteral("count")].toInt(); 50 | info.second = pair[QStringLiteral("changed")].toBool(); 51 | } 52 | v.insert(static_cast(metaEnum.keyToValue(key.toLatin1().constData())), info); 53 | } 54 | return QVariant::fromValue(v); 55 | } 56 | -------------------------------------------------------------------------------- /gui/core/loginviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGINVIEWMODEL_H 2 | #define LOGINVIEWMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class LoginViewModel : public QtMvvm::ViewModel 9 | { 10 | Q_OBJECT 11 | 12 | Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged) 13 | Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) 14 | Q_PROPERTY(QString authCode READ authCode WRITE setAuthCode NOTIFY authCodeChanged) 15 | Q_PROPERTY(bool storePassword READ storePassword WRITE setStorePassword NOTIFY storePasswordChanged) 16 | 17 | Q_PROPERTY(bool loading READ isLoading NOTIFY loadingChanged) 18 | 19 | QTMVVM_INJECT_PROP(LocalSettings*, settings, _settings) 20 | 21 | public: 22 | Q_INVOKABLE explicit LoginViewModel(QObject *parent = nullptr); 23 | 24 | QString userName() const; 25 | QString password() const; 26 | QString authCode() const; 27 | bool storePassword() const; 28 | bool isLoading() const; 29 | 30 | public slots: 31 | void setUserName(const QString &userName); 32 | void setPassword(const QString &password); 33 | void setAuthCode(const QString &authCode); 34 | void setStorePassword(bool storePassword); 35 | 36 | void login(); 37 | 38 | signals: 39 | void userNameChanged(const QString &userName); 40 | void passwordChanged(const QString &password); 41 | void authCodeChanged(const QString &authCode); 42 | void storePasswordChanged(bool storePassword); 43 | void loadingChanged(bool loading); 44 | 45 | void loginSuccessful(); 46 | 47 | protected: 48 | void onInit(const QVariantHash ¶ms) override; 49 | 50 | private slots: 51 | void apiError(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType); 52 | 53 | private: 54 | LocalSettings *_settings; 55 | UserClass *_user; 56 | 57 | QString _userName; 58 | QString _password; 59 | QString _authCode; 60 | bool _storePassword; 61 | bool _loading; 62 | }; 63 | 64 | #endif // LOGINVIEWMODEL_H 65 | -------------------------------------------------------------------------------- /gui/widgets/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mainwindow.h" 9 | #include "addanimedialog.h" 10 | #include "detailsdockwidget.h" 11 | #include "entrydialog.h" 12 | #include "logindialog.h" 13 | #include "widgetsupdatenotifier.h" 14 | #include "instancesetup.h" 15 | 16 | // Register the core app 17 | QTMVVM_REGISTER_CORE_APP(AniRemApp) 18 | 19 | namespace { 20 | 21 | QPointer currentMainWindow; 22 | QPointer currentInstance; 23 | 24 | } 25 | 26 | int main(int argc, char *argv[]) 27 | { 28 | QtMvvm::CoreApp::disableAutoBoot(); 29 | 30 | QApplication a(argc, argv); 31 | 32 | QSingleInstance instance; 33 | currentInstance = &instance; 34 | instance.setStartupFunction([&](){ 35 | QtMvvm::ServiceRegistry::instance()->registerInterface(); 36 | 37 | QtMvvm::registerDataSyncWidgets(); 38 | QtMvvm::WidgetsPresenter::registerView(); 39 | QtMvvm::WidgetsPresenter::registerView(); 40 | QtMvvm::WidgetsPresenter::registerView(); 41 | QtMvvm::WidgetsPresenter::registerView(); 42 | QtMvvm::WidgetsPresenter::registerView(); 43 | 44 | QObject::connect(&instance, &QSingleInstance::instanceMessage, 45 | coreApp, [](){ 46 | if(!currentMainWindow) 47 | coreApp->show(); 48 | }); 49 | 50 | //workaround to force svg deployment 51 | QSvgRenderer _unused; 52 | 53 | coreApp->bootApp(); 54 | return EXIT_SUCCESS; 55 | }); 56 | 57 | return instance.singleExec(); 58 | } 59 | 60 | void setMainWindow(QMainWindow *window) 61 | { 62 | currentMainWindow = window; 63 | if(currentInstance) 64 | currentInstance->setNotifyWindow(window); 65 | } 66 | 67 | QMainWindow *getMainWindow() 68 | { 69 | return currentMainWindow; 70 | } 71 | -------------------------------------------------------------------------------- /gui/core/proxerentrymodel.h: -------------------------------------------------------------------------------- 1 | #ifndef PROXERENTRYMODEL_H 2 | #define PROXERENTRYMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class ProxerEntryModel : public QAbstractTableModel 9 | { 10 | Q_OBJECT 11 | 12 | Q_PROPERTY(ListType listType READ listType WRITE setListType NOTIFY listTypeChanged) 13 | 14 | public: 15 | enum ListType { 16 | ListAnimes = 0, 17 | ListMangas = 1 18 | }; 19 | Q_ENUM(ListType) 20 | 21 | enum Roles { 22 | NameRole = Qt::DisplayRole, 23 | IdRole = Qt::UserRole + 1, 24 | TypeRole, 25 | DateRole, 26 | RatingRole, 27 | 28 | InvalidRole = -1 29 | }; 30 | Q_ENUM(Roles) 31 | 32 | static const int PageSize; 33 | 34 | explicit ProxerEntryModel(SyncedSettings *settings, QObject *parent = nullptr); 35 | 36 | int getId(const QModelIndex &index) const; 37 | 38 | int rowCount(const QModelIndex &parent = {}) const override; 39 | int columnCount(const QModelIndex &parent = {}) const override; 40 | QHash roleNames() const override; 41 | 42 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 43 | 44 | bool canFetchMore(const QModelIndex &parent = {}) const override; 45 | void fetchMore(const QModelIndex &parent = {}) override; 46 | 47 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 48 | 49 | ListType listType() const; 50 | 51 | public slots: 52 | void setListType(ListType listType); 53 | 54 | signals: 55 | void loginNeeded(); 56 | void apiError(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType); 57 | 58 | void listTypeChanged(ListType listType); 59 | 60 | private: 61 | SyncedSettings *_settings; 62 | UserClass *_user; 63 | ListType _listType; 64 | bool _isFetching; 65 | bool _skipNext; 66 | 67 | QList _data; 68 | 69 | Roles indexRole(const QModelIndex &index, int role) const; 70 | QString typeName(ListType listType) const; 71 | }; 72 | 73 | #endif // PROXERENTRYMODEL_H 74 | -------------------------------------------------------------------------------- /droid-service/android/src/de/skycoder42/anirem/BootReceiver.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.SharedPreferences; 7 | 8 | import android.app.PendingIntent; 9 | import android.app.AlarmManager; 10 | 11 | public class BootReceiver extends BroadcastReceiver { 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | scheduleAutoCheck(context); 16 | } 17 | 18 | static public void scheduleAutoCheck(Context context) { 19 | SharedPreferences prefs = context.getSharedPreferences(Globals.AutoStartPrefs, Context.MODE_PRIVATE); 20 | scheduleAutoCheckImp(context, prefs.getBoolean(Globals.AutStartKey, true)); 21 | } 22 | 23 | static public void scheduleAutoCheck(Context context, boolean autoCheck) { 24 | scheduleAutoCheckImp(context, autoCheck); 25 | 26 | SharedPreferences prefs = context.getSharedPreferences(Globals.AutoStartPrefs, Context.MODE_PRIVATE); 27 | SharedPreferences.Editor editor = prefs.edit(); 28 | editor.putBoolean(Globals.AutStartKey, autoCheck); 29 | editor.apply(); 30 | } 31 | 32 | static private void scheduleAutoCheckImp(Context context, boolean autoCheck) { 33 | Intent intent = new Intent(context, AniremService.class); 34 | PendingIntent pending; 35 | if(Globals.isOreo()) 36 | pending = PendingIntent.getForegroundService(context, Globals.AlarmIntentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 37 | else 38 | pending = PendingIntent.getService(context, Globals.AlarmIntentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 39 | 40 | AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 41 | if(autoCheck) { 42 | alarm.setRepeating(AlarmManager.RTC_WAKEUP, 43 | System.currentTimeMillis() + AlarmManager.INTERVAL_DAY, 44 | AlarmManager.INTERVAL_DAY - 3600000, //every 23 hours, to rotate over time 45 | pending); 46 | } else { 47 | alarm.cancel(pending); 48 | pending.cancel(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gui/quick/android/src/de/skycoder42/anirem/BootReceiver.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.SharedPreferences; 7 | 8 | import android.app.PendingIntent; 9 | import android.app.AlarmManager; 10 | 11 | public class BootReceiver extends BroadcastReceiver { 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | scheduleAutoCheck(context); 16 | } 17 | 18 | static public void scheduleAutoCheck(Context context) { 19 | SharedPreferences prefs = context.getSharedPreferences(Globals.AutoStartPrefs, Context.MODE_PRIVATE); 20 | scheduleAutoCheckImp(context, prefs.getBoolean(Globals.AutStartKey, true)); 21 | } 22 | 23 | static public void scheduleAutoCheck(Context context, boolean autoCheck) { 24 | scheduleAutoCheckImp(context, autoCheck); 25 | 26 | SharedPreferences prefs = context.getSharedPreferences(Globals.AutoStartPrefs, Context.MODE_PRIVATE); 27 | SharedPreferences.Editor editor = prefs.edit(); 28 | editor.putBoolean(Globals.AutStartKey, autoCheck); 29 | editor.apply(); 30 | } 31 | 32 | static private void scheduleAutoCheckImp(Context context, boolean autoCheck) { 33 | Intent intent = new Intent(context, AniremService.class); 34 | PendingIntent pending; 35 | if(Globals.isOreo()) 36 | pending = PendingIntent.getForegroundService(context, Globals.AlarmIntentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 37 | else 38 | pending = PendingIntent.getService(context, Globals.AlarmIntentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 39 | 40 | AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 41 | if(autoCheck) { 42 | alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, 43 | System.currentTimeMillis() + AlarmManager.INTERVAL_DAY, 44 | AlarmManager.INTERVAL_DAY - 3600000, //every 23 hours, to rotate over time 45 | pending); 46 | } else { 47 | alarm.cancel(pending); 48 | pending.cancel(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/storedcookiejar.cpp: -------------------------------------------------------------------------------- 1 | #include "storedcookiejar.h" 2 | #include 3 | #include 4 | #include 5 | 6 | const QString StoredCookieJar::CookieKey = QStringLiteral("cookies"); 7 | const QString StoredCookieJar::CookieDataKey = QStringLiteral("raw"); 8 | 9 | StoredCookieJar::StoredCookieJar(QObject *parent) : 10 | QNetworkCookieJar(parent), 11 | _cookieStore(nullptr), 12 | _scheduled(false) 13 | { 14 | QDir cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 15 | cacheDir.mkpath(QStringLiteral(".")); 16 | _cookieStore = new QSettings(cacheDir.absoluteFilePath(QStringLiteral("api-cookie-jar.ini")), 17 | QSettings::IniFormat, 18 | this); 19 | auto cnt = _cookieStore->beginReadArray(CookieKey); 20 | QList cookies; 21 | cookies.reserve(cnt); 22 | for(auto i = 0; i < cnt; i++) { 23 | _cookieStore->setArrayIndex(i); 24 | cookies.append(QNetworkCookie::parseCookies(_cookieStore->value(CookieDataKey).toByteArray())); 25 | } 26 | _cookieStore->endArray(); 27 | setAllCookies(cookies); 28 | } 29 | 30 | StoredCookieJar::~StoredCookieJar() 31 | { 32 | if(_scheduled) 33 | store(); 34 | } 35 | 36 | bool StoredCookieJar::insertCookie(const QNetworkCookie &cookie) 37 | { 38 | auto ok = QNetworkCookieJar::insertCookie(cookie); 39 | if(ok && !_scheduled) 40 | _scheduled = QMetaObject::invokeMethod(this, "store", Qt::QueuedConnection); 41 | return ok; 42 | } 43 | 44 | bool StoredCookieJar::deleteCookie(const QNetworkCookie &cookie) 45 | { 46 | auto ok = QNetworkCookieJar::deleteCookie(cookie); 47 | if(ok && !_scheduled) 48 | _scheduled = QMetaObject::invokeMethod(this, "store", Qt::QueuedConnection); 49 | return ok; 50 | } 51 | 52 | void StoredCookieJar::store() 53 | { 54 | auto cookies = allCookies(); 55 | _cookieStore->remove(CookieKey); 56 | _cookieStore->beginWriteArray(CookieKey, cookies.size()); 57 | auto index = 0; 58 | for(auto cookie : cookies) { 59 | _cookieStore->setArrayIndex(index++); 60 | _cookieStore->setValue(CookieDataKey, cookie.toRawForm(QNetworkCookie::Full)); 61 | } 62 | _cookieStore->endArray(); 63 | _scheduled = false; 64 | } 65 | -------------------------------------------------------------------------------- /gui/widgets/detailsdockwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "detailsdockwidget.h" 2 | #include "ui_detailsdockwidget.h" 3 | 4 | #include 5 | #include 6 | 7 | DetailsDockWidget::DetailsDockWidget(QtMvvm::ViewModel *viewModel, QWidget *parent) : 8 | QDockWidget(parent), 9 | _viewModel(static_cast(viewModel)), 10 | _ui(new Ui::DetailsDockWidget) 11 | { 12 | _ui->setupUi(this); 13 | setMinimumWidth(_ui->previewLabel->minimumWidth() + 14 | 8 + 15 | style()->pixelMetric(QStyle::PM_ScrollBarExtent)); 16 | 17 | connect(_viewModel->imageLoader(), &ImageLoader::imageLoaded, 18 | this, &DetailsDockWidget::imageLoaded); 19 | connect(_viewModel->imageLoader(), &ImageLoader::imageLoadFailed, 20 | this, &DetailsDockWidget::imageLoadFailed); 21 | 22 | connect(_viewModel, &DetailsViewModel::animeInfoChanged, 23 | this, &DetailsDockWidget::updateInfo); 24 | updateInfo(); 25 | 26 | QMetaObject::invokeMethod(parent, "addDockAction", 27 | Q_ARG(QAction*, toggleViewAction())); 28 | } 29 | 30 | DetailsDockWidget::~DetailsDockWidget() 31 | { 32 | delete _ui; 33 | } 34 | 35 | void DetailsDockWidget::closeEvent(QCloseEvent *event) 36 | { 37 | event->ignore(); 38 | hide(); 39 | } 40 | 41 | void DetailsDockWidget::updateInfo() 42 | { 43 | auto info = _viewModel->animeInfo(); 44 | if(info) { 45 | setWindowTitle(tr("%1 Details").arg(info.title())); 46 | 47 | _ui->previewLabel->setText(tr("Loading preview image…")); 48 | _viewModel->imageLoader()->loadImage(info.id()); 49 | _ui->detailsLabel->setText(_viewModel->detailsText()); 50 | } else { 51 | setWindowTitle(tr("Anime Details")); 52 | _ui->previewLabel->clear(); 53 | _ui->detailsLabel->clear(); 54 | } 55 | } 56 | 57 | void DetailsDockWidget::imageLoaded(int id, const QImage &image) 58 | { 59 | if(_viewModel->animeInfo() && _viewModel->animeInfo().id() == id) 60 | _ui->previewLabel->setPixmap(QPixmap::fromImage(image)); 61 | } 62 | 63 | void DetailsDockWidget::imageLoadFailed(int id, const QString &error) 64 | { 65 | if(_viewModel->animeInfo() && _viewModel->animeInfo().id() == id) { 66 | qWarning().noquote() << error; 67 | _ui->previewLabel->setText(tr("No image")); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gui/core/addanimeviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDANIMEVIEWMODEL_H 2 | #define ADDANIMEVIEWMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "imageloader.h" 11 | 12 | class AddAnimeViewModel : public QtMvvm::ViewModel 13 | { 14 | Q_OBJECT 15 | 16 | Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged) 17 | Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) 18 | Q_PROPERTY(QStringListModel* nameModel READ nameModel CONSTANT) 19 | Q_PROPERTY(bool loading READ isLoading NOTIFY loadingChanged) 20 | Q_PROPERTY(bool acceptable READ isAcceptable NOTIFY acceptableChanged) 21 | 22 | QTMVVM_INJECT_PROP(ImageLoader*, imageLoader, _loader) 23 | QTMVVM_INJECT_PROP(SeasonStatusLoader*, updater, _updater) 24 | 25 | public: 26 | static const QString ParamId; 27 | 28 | static QVariantHash params(int id); 29 | 30 | Q_INVOKABLE explicit AddAnimeViewModel(QObject *parent = nullptr); 31 | 32 | int id() const; 33 | QString title() const; 34 | QStringListModel* nameModel() const; 35 | bool isLoading() const; 36 | bool isAcceptable() const; 37 | 38 | ImageLoader* imageLoader() const; 39 | 40 | public slots: 41 | bool accept(bool allowInvalid = false); 42 | 43 | void setId(int id); 44 | void setTitle(QString title); 45 | 46 | signals: 47 | void close(); 48 | 49 | void idChanged(int id); 50 | void titleChanged(QString title); 51 | void allNamesChanged(QStringList allNames); 52 | void loadingChanged(bool loading); 53 | void acceptableChanged(bool acceptable); 54 | 55 | protected: 56 | void onInit(const QVariantHash ¶ms) override; 57 | 58 | private slots: 59 | void error(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType); 60 | 61 | private: 62 | AniremStore *_store; 63 | InfoClass *_infoClass; 64 | ImageLoader *_loader; 65 | SeasonStatusLoader *_updater; 66 | QStringListModel *_nameModel; 67 | 68 | int _id; 69 | QString _title; 70 | bool _loading; 71 | bool _acceptable; 72 | 73 | void updateLoading(bool loading); 74 | void setAcceptable(bool acceptable); 75 | }; 76 | 77 | #endif // ADDANIMEVIEWMODEL_H 78 | -------------------------------------------------------------------------------- /gui/quick/AnimeInfoDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Controls.Material 2.3 4 | import QtQuick.Controls.Universal 2.3 5 | import QtQuick.Layouts 1.3 6 | import de.skycoder42.QtMvvm.Quick 1.0 7 | 8 | SwipeDelegate { 9 | id: delegate 10 | width: parent.width 11 | text: title 12 | highlighted: hasNewSeasons === "true" 13 | 14 | signal animeDeleted 15 | 16 | function redColor() { 17 | if(QuickPresenter.currentStyle === "Material") 18 | return Material.color(Material.Red); 19 | else if(QuickPresenter.currentStyle === "Universal") 20 | return Universal.color(Universal.Red); 21 | else 22 | return "#FF0000"; 23 | } 24 | 25 | contentItem: RowLayout { 26 | Image { 27 | id: previewImage 28 | asynchronous: true 29 | fillMode: Image.PreserveAspectFit 30 | horizontalAlignment: Image.AlignHCenter 31 | verticalAlignment: Image.AlignVCenter 32 | Layout.minimumWidth: 42 33 | Layout.maximumWidth: 42 34 | Layout.minimumHeight: 42 35 | Layout.maximumHeight: 42 36 | source: "image://proxer/%1".arg(id) 37 | mipmap: true 38 | } 39 | 40 | Label { 41 | id: titleLabel 42 | elide: Label.ElideRight 43 | horizontalAlignment: Qt.AlignLeft 44 | verticalAlignment: Qt.AlignVCenter 45 | Layout.fillWidth: true 46 | Layout.fillHeight: true 47 | font.bold: delegate.highlighted 48 | Material.foreground: delegate.highlighted ? Material.accent : delegate.Material.foreground 49 | text: delegate.text 50 | } 51 | 52 | Label { 53 | id: countLabel 54 | Layout.fillHeight: true 55 | horizontalAlignment: Qt.AlignRight 56 | verticalAlignment: Qt.AlignVCenter 57 | Material.foreground: delegate.highlighted ? Material.accent : delegate.Material.foreground 58 | text: totalSeasonCount 59 | } 60 | } 61 | 62 | swipe.right: Rectangle { 63 | anchors.fill: parent 64 | color: redColor() 65 | 66 | MouseArea { 67 | anchors.fill: parent 68 | 69 | ActionButton { 70 | anchors.centerIn: parent 71 | icon.name: "user-trash" 72 | icon.source: "qrc:/icons/ic_delete_forever.svg" 73 | text: qsTr("Delete Anime") 74 | 75 | Material.foreground: "white" 76 | Universal.foreground: "white" 77 | 78 | onClicked: { 79 | delegate.swipe.close(); 80 | animeDeleted() 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /gui/quick/proxerimageprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "proxerimageprovider.h" 2 | #include 3 | 4 | ProxerImageProvider::ProxerImageProvider() : 5 | QQuickAsyncImageProvider(), 6 | _loader(QtMvvm::ServiceRegistry::instance()->service()) 7 | {} 8 | 9 | QQmlImageProviderBase::ImageType ProxerImageProvider::imageType() const 10 | { 11 | return QQmlImageProviderBase::ImageResponse; 12 | } 13 | 14 | QQmlImageProviderBase::Flags ProxerImageProvider::flags() const 15 | { 16 | return QQmlImageProviderBase::ForceAsynchronousImageLoading; 17 | } 18 | 19 | QQuickImageResponse *ProxerImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) 20 | { 21 | auto ok = false; 22 | auto rId = id.toInt(&ok); 23 | if(ok) { 24 | auto response = new ProxerImageResponse(rId, requestedSize); 25 | QObject::connect(_loader, &ImageLoader::imageLoaded, 26 | response, &ProxerImageResponse::imageLoaded, 27 | Qt::QueuedConnection); 28 | QObject::connect(_loader, &ImageLoader::imageLoadFailed, 29 | response, &ProxerImageResponse::imageLoadFailed, 30 | Qt::QueuedConnection); 31 | _loader->loadImage(rId); 32 | return response; 33 | } else 34 | return nullptr; 35 | } 36 | 37 | 38 | 39 | ProxerImageResponse::ProxerImageResponse(int id, const QSize &requestedSize) : 40 | _id(id), 41 | _requestedSize(requestedSize), 42 | _result(), 43 | _error() 44 | {} 45 | 46 | QQuickTextureFactory *ProxerImageResponse::textureFactory() const 47 | { 48 | if(_result.isNull()) 49 | return nullptr; 50 | else 51 | return QQuickTextureFactory::textureFactoryForImage(_result); 52 | } 53 | 54 | QString ProxerImageResponse::errorString() const 55 | { 56 | return _error; 57 | } 58 | 59 | void ProxerImageResponse::cancel() 60 | { 61 | _id = -1; 62 | _result = QImage(); 63 | _error.clear(); 64 | emit finished(); 65 | } 66 | 67 | void ProxerImageResponse::imageLoaded(int id, const QImage &image) 68 | { 69 | if(id == _id) { 70 | if(_requestedSize.isValid()) 71 | _result = image.scaled(_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); 72 | else 73 | _result = image; 74 | emit finished(); 75 | } 76 | } 77 | 78 | void ProxerImageResponse::imageLoadFailed(int id, const QString &errorString) 79 | { 80 | if(id == _id) { 81 | _error = errorString; 82 | emit finished(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /gui/core/entryviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "entryviewmodel.h" 2 | #include 3 | #include 4 | #include 5 | #include "loginviewmodel.h" 6 | #include "addanimeviewmodel.h" 7 | 8 | EntryViewModel::EntryViewModel(QObject *parent) : 9 | ViewModel(parent), 10 | _settings(nullptr), 11 | _model(nullptr), 12 | _showingChild(false) 13 | {} 14 | 15 | ProxerEntryModel *EntryViewModel::model() const 16 | { 17 | return _model; 18 | } 19 | 20 | int EntryViewModel::typeIndex() const 21 | { 22 | return _model ? _model->listType() : ProxerEntryModel::ListAnimes; 23 | } 24 | 25 | QStringList EntryViewModel::listTypes() const 26 | { 27 | return { 28 | tr("Animes"), 29 | tr("Mangas") 30 | }; 31 | } 32 | 33 | void EntryViewModel::addAnime(int id) 34 | { 35 | if(id == -1) 36 | return; 37 | 38 | if(!_showingChild) { 39 | _showingChild = true; 40 | showForResult(AddRequestCode, AddAnimeViewModel::params(id)); 41 | } 42 | } 43 | 44 | void EntryViewModel::setTypeIndex(int typeIndex) 45 | { 46 | if(_model) 47 | _model->setListType(static_cast(typeIndex)); 48 | } 49 | 50 | void EntryViewModel::onInit(const QVariantHash ¶ms) 51 | { 52 | Q_UNUSED(params) 53 | 54 | _model = new ProxerEntryModel(_settings, this); 55 | connect(_model, &ProxerEntryModel::listTypeChanged, 56 | this, &EntryViewModel::typeIndexChanged); 57 | connect(_model, &ProxerEntryModel::loginNeeded, 58 | this, &EntryViewModel::loginNeeded); 59 | connect(_model, &ProxerEntryModel::apiError, 60 | this, &EntryViewModel::apiError); 61 | emit modelChanged(_model); 62 | } 63 | 64 | void EntryViewModel::onResult(quint32 requestCode, const QVariant &result) 65 | { 66 | switch(requestCode) { 67 | case LoginRequestCode: 68 | _showingChild = false; 69 | if(result.toBool()) 70 | _model->fetchMore(); 71 | break; 72 | case AddRequestCode: 73 | _showingChild = false; 74 | break; 75 | default: 76 | break; 77 | } 78 | } 79 | 80 | void EntryViewModel::loginNeeded() 81 | { 82 | if(!_showingChild) { 83 | _showingChild = true; 84 | showForResult(LoginRequestCode); 85 | } 86 | } 87 | 88 | void EntryViewModel::apiError(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType) 89 | { 90 | QtMvvm::critical(tr("Failed to get entries"), 91 | ApiHelper::formatError(errorString, errorCode, errorType)); 92 | } 93 | -------------------------------------------------------------------------------- /lib/passiveupdater.cpp: -------------------------------------------------------------------------------- 1 | #include "passiveupdater.h" 2 | #include 3 | 4 | PassiveUpdater::PassiveUpdater(QObject *parent) : 5 | QObject(parent), 6 | _loader(nullptr), 7 | _notifier(nullptr), 8 | _manager(new QtDataSync::SyncManager(this)), 9 | _store(new AniremStore(this)) 10 | {} 11 | 12 | void PassiveUpdater::performUpdateCheck() 13 | { 14 | //WORKAROUND remove once patched 15 | qtmvvm_init(); 16 | 17 | auto startFn = [this]() { 18 | _manager->runOnSynchronized([this](QtDataSync::SyncManager::SyncState state){ 19 | Q_UNUSED(state) 20 | _loader->checkForUpdates(true); 21 | }, false); 22 | }; 23 | 24 | if(_manager->replica()->isInitialized()) 25 | startFn(); 26 | else { 27 | connect(_manager->replica(), &QRemoteObjectReplica::initialized, 28 | this, startFn); 29 | } 30 | } 31 | 32 | void PassiveUpdater::qtmvvm_init() 33 | { 34 | connect(_loader, &SeasonStatusLoader::completed, 35 | this, &PassiveUpdater::completed); 36 | connect(_loader, SIGNAL(updated(int,int)), 37 | dynamic_cast(_notifier), SLOT(updateProgress(int,int))); 38 | } 39 | 40 | void PassiveUpdater::completed(bool hasUpdates, const QString &errorString) 41 | { 42 | if(!errorString.isEmpty()) { 43 | _notifier->showNotification(false, tr("Season check failed!"), errorString); 44 | emit done(true); 45 | } else { 46 | try { 47 | // show the notification 48 | auto allInfos = _store->loadAll(); 49 | QStringList updatesList; 50 | for(const auto &anime : allInfos) { 51 | if(anime.hasNewSeasons()) { 52 | updatesList.append(tr("• %1 has now %n relation(s)", "", anime.totalSeasonCount()) 53 | .arg(anime.title())); 54 | } 55 | } 56 | 57 | if(!updatesList.isEmpty()) { 58 | _notifier->showNotification(true, 59 | tr("%n new relation(s) detected!", "", updatesList.size()), 60 | updatesList.join(QLatin1Char('\n'))); 61 | } 62 | 63 | // if updated: sync then done, otherwise done 64 | if(hasUpdates) { 65 | _manager->runOnSynchronized([this](QtDataSync::SyncManager::SyncState state){ 66 | Q_UNUSED(state) 67 | emit done(true); 68 | }); 69 | } else 70 | emit done(!updatesList.isEmpty()); 71 | } catch(QException &e) { 72 | qCritical() << "Failed to present update result with error" << e.what(); 73 | _notifier->showNotification(false, tr("Season check failed!"), tr("Failed to load info of updated Animes")); 74 | emit done(true); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /droid-service/android/src/de/skycoder42/anirem/AniremActivity.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.os.Bundle; 4 | import android.os.IBinder; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.ServiceConnection; 9 | import android.content.ComponentName; 10 | 11 | import android.app.NotificationManager; 12 | import android.app.NotificationChannel; 13 | 14 | import org.qtproject.qt5.android.bindings.QtActivity; 15 | 16 | public class AniremActivity extends QtActivity { 17 | private boolean _isBound; 18 | private ServiceConnection _connection = new ServiceConnection() { 19 | @Override 20 | public void onServiceConnected(ComponentName className, IBinder service) { 21 | _isBound = true; 22 | } 23 | 24 | @Override 25 | public void onServiceDisconnected(ComponentName className) { 26 | _isBound = false; 27 | } 28 | }; 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | createChannels(); 34 | 35 | Intent intent = new Intent(this, AniremService.class); 36 | bindService(intent, _connection, Context.BIND_AUTO_CREATE); 37 | } 38 | 39 | @Override 40 | public void onDestroy() { 41 | super.onDestroy(); 42 | if (_isBound) { 43 | unbindService(_connection); 44 | _isBound = false; 45 | } 46 | } 47 | 48 | private void createChannels() { 49 | if(!Globals.isOreo()) 50 | return; 51 | 52 | NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 53 | 54 | //create normal channel 55 | NotificationChannel normal = new NotificationChannel(Globals.NormalChannelId, 56 | getString(R.string.channel_nrm_name), 57 | NotificationManager.IMPORTANCE_DEFAULT); 58 | normal.setDescription(getString(R.string.channel_nrm_desc)); 59 | normal.enableLights(true); 60 | normal.setLightColor(Globals.NormalColor); 61 | normal.enableVibration(true); 62 | normal.setShowBadge(true); 63 | manager.createNotificationChannel(normal); 64 | 65 | //create foreground channel 66 | NotificationChannel foreground = new NotificationChannel(Globals.ForegroundChannelId, 67 | getString(R.string.channel_svc_name), 68 | NotificationManager.IMPORTANCE_MIN); 69 | foreground.setDescription(getString(R.string.channel_svc_desc)); 70 | foreground.enableLights(false); 71 | foreground.enableVibration(false); 72 | foreground.setShowBadge(false); 73 | manager.createNotificationChannel(foreground); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gui/quick/android/src/de/skycoder42/anirem/AniremActivity.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.os.Bundle; 4 | import android.os.IBinder; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.ServiceConnection; 9 | import android.content.ComponentName; 10 | 11 | import android.app.NotificationManager; 12 | import android.app.NotificationChannel; 13 | 14 | import org.qtproject.qt5.android.bindings.QtActivity; 15 | 16 | public class AniremActivity extends QtActivity { 17 | private boolean _isBound; 18 | private ServiceConnection _connection = new ServiceConnection() { 19 | @Override 20 | public void onServiceConnected(ComponentName className, IBinder service) { 21 | _isBound = true; 22 | } 23 | 24 | @Override 25 | public void onServiceDisconnected(ComponentName className) { 26 | _isBound = false; 27 | } 28 | }; 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | createChannels(); 34 | 35 | Intent intent = new Intent(this, AniremService.class); 36 | bindService(intent, _connection, Context.BIND_AUTO_CREATE); 37 | } 38 | 39 | @Override 40 | public void onDestroy() { 41 | super.onDestroy(); 42 | if (_isBound) { 43 | unbindService(_connection); 44 | _isBound = false; 45 | } 46 | } 47 | 48 | private void createChannels() { 49 | if(!Globals.isOreo()) 50 | return; 51 | 52 | NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 53 | 54 | //create normal channel 55 | NotificationChannel normal = new NotificationChannel(Globals.NormalChannelId, 56 | getString(R.string.channel_nrm_name), 57 | NotificationManager.IMPORTANCE_DEFAULT); 58 | normal.setDescription(getString(R.string.channel_nrm_desc)); 59 | normal.enableLights(true); 60 | normal.setLightColor(Globals.NormalColor); 61 | normal.enableVibration(true); 62 | normal.setShowBadge(true); 63 | manager.createNotificationChannel(normal); 64 | 65 | //create foreground channel 66 | NotificationChannel foreground = new NotificationChannel(Globals.ForegroundChannelId, 67 | getString(R.string.channel_svc_name), 68 | NotificationManager.IMPORTANCE_MIN); 69 | foreground.setDescription(getString(R.string.channel_svc_desc)); 70 | foreground.enableLights(false); 71 | foreground.enableVibration(false); 72 | foreground.setShowBadge(false); 73 | manager.createNotificationChannel(foreground); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | VERSION = 2.0.1 2 | 3 | CONFIG += warning_clean exceptions 4 | android|clang: CONFIG += c++14 5 | else: CONFIG += c++17 6 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS QT_USE_QSTRINGBUILDER 7 | 8 | QMAKE_TARGET_COMPANY = "Skycoder42" 9 | QMAKE_TARGET_DESCRIPTION = "Anime season reminder" 10 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 11 | QMAKE_TARGET_BUNDLE_PREFIX = de.skycoder42 12 | 13 | DEFINES += "VERSION=\\\"$$VERSION\\\"" 14 | DEFINES += "COMPANY=\"\\\"$$QMAKE_TARGET_COMPANY\\\"\"" 15 | DEFINES += "BUNDLE=\"\\\"$$QMAKE_TARGET_BUNDLE_PREFIX\\\"\"" 16 | 17 | # installing 18 | exists($$PWD/install.pri):include($$PWD/install.pri) 19 | 20 | android: INSTALL_TRANSLATIONS = /assets/translations 21 | else:create_installer { 22 | CONFIG += no_quick 23 | QTIFW_PKG_ROOT = /packages/de.skycoder42.anirem 24 | INSTALL_PREFIX = $$QTIFW_PKG_ROOT/data 25 | !mac: INSTALL_BINS = $$INSTALL_PREFIX 26 | win32: INSTALL_LIBS = $$INSTALL_BINS 27 | } 28 | 29 | isEmpty(INSTALL_PREFIX) { 30 | INSTALL_PREFIX = $$[QT_INSTALL_PREFIX] 31 | isEmpty(INSTALL_BINS): INSTALL_BINS = $$[QT_INSTALL_BINS] 32 | isEmpty(INSTALL_LIBS): INSTALL_LIBS = $$[QT_INSTALL_LIBS] 33 | isEmpty(INSTALL_HEADERS): INSTALL_HEADERS = $$[QT_INSTALL_HEADERS] 34 | isEmpty(INSTALL_PLUGINS): INSTALL_PLUGINS = $$[QT_INSTALL_PLUGINS] 35 | isEmpty(INSTALL_TRANSLATIONS): INSTALL_TRANSLATIONS = $$[QT_INSTALL_TRANSLATIONS] 36 | } else:mac { 37 | APP_PREFIX = Ani-Rem.app/Contents 38 | isEmpty(INSTALL_APPS): INSTALL_APPS = $${INSTALL_PREFIX}/.app-tmp 39 | isEmpty(INSTALL_BINS): INSTALL_BINS = $${INSTALL_PREFIX}/$${APP_PREFIX}/MacOS 40 | isEmpty(INSTALL_LIBS): INSTALL_LIBS = $${INSTALL_PREFIX}/$${APP_PREFIX}/Frameworks 41 | isEmpty(INSTALL_HEADERS): INSTALL_HEADERS = $${INSTALL_PREFIX}/$${APP_PREFIX}/Headers 42 | isEmpty(INSTALL_PLUGINS): INSTALL_PLUGINS = $${INSTALL_PREFIX}/$${APP_PREFIX}/PlugIns 43 | isEmpty(INSTALL_TRANSLATIONS): INSTALL_TRANSLATIONS = $${INSTALL_PREFIX}/$${APP_PREFIX}/Resources/translations 44 | isEmpty(INSTALL_SHARE): INSTALL_SHARE = $${INSTALL_PREFIX}/$${APP_PREFIX}/Resources 45 | } else { 46 | isEmpty(INSTALL_BINS): INSTALL_BINS = $${INSTALL_PREFIX}/bin 47 | isEmpty(INSTALL_LIBS): INSTALL_LIBS = $${INSTALL_PREFIX}/lib 48 | isEmpty(INSTALL_HEADERS): INSTALL_HEADERS = $${INSTALL_PREFIX}/include 49 | isEmpty(INSTALL_PLUGINS): INSTALL_PLUGINS = $${INSTALL_PREFIX}/plugins 50 | isEmpty(INSTALL_TRANSLATIONS): INSTALL_TRANSLATIONS = $${INSTALL_PREFIX}/translations 51 | } 52 | isEmpty(INSTALL_SHARE): INSTALL_SHARE = $${INSTALL_PREFIX}/share 53 | -------------------------------------------------------------------------------- /gui/core/mainviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINVIEWMODEL_H 2 | #define MAINVIEWMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | class DetailsViewModel; 12 | 13 | class MainViewModel : public QtMvvm::ViewModel 14 | { 15 | Q_OBJECT 16 | 17 | Q_PROPERTY(QtDataSync::DataStoreModel* animeModel READ animeModel CONSTANT) 18 | Q_PROPERTY(QSortFilterProxyModel* sortedModel READ sortedModel CONSTANT) 19 | Q_PROPERTY(bool reloadingAnimes READ isReloadingAnimes NOTIFY reloadingAnimesChanged) 20 | Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged) 21 | 22 | QTMVVM_INJECT_PROP(SyncedSettings*, settings, _settings) 23 | QTMVVM_INJECT_PROP(SeasonStatusLoader*, updater, _updater) 24 | 25 | public: 26 | Q_INVOKABLE explicit MainViewModel(QObject *parent = nullptr); 27 | 28 | QtDataSync::DataStoreModel* animeModel() const; 29 | QSortFilterProxyModel* sortedModel() const; 30 | 31 | bool isReloadingAnimes() const; 32 | 33 | QString filterString() const; 34 | 35 | public slots: 36 | void reload(); 37 | void showSettings(); 38 | void showSync(); 39 | void showAbout(); 40 | void showCaptcha(); 41 | 42 | void uncheckAnime(int id); 43 | void itemAction(int id); 44 | 45 | void addAnime(); 46 | void addAnimeBlank(); 47 | void addAnimeFromEntryList(); 48 | void addAnimeFromClipboard(); 49 | void showDetails(int id); 50 | void openUrl(int id); 51 | 52 | void removeAnime(int id); 53 | 54 | void setFilterString(QString filterString); 55 | 56 | signals: 57 | void showStatus(const QString &message); 58 | void setProgress(int value, int max); 59 | 60 | void reloadingAnimesChanged(bool reloadingAnimes); 61 | 62 | void filterStringChanged(QString filterString); 63 | 64 | protected: 65 | void onInit(const QVariantHash ¶ms) override; 66 | 67 | private slots: 68 | void setDetailsView(QPointer details); 69 | void updaterStarted(); 70 | void updaterDone(bool hasUpdates, const QString &error); 71 | 72 | private: 73 | QtDataSync::DataStoreModel *_model; 74 | QSortFilterProxyModel *_sortModel; 75 | SyncedSettings *_settings; 76 | SeasonStatusLoader *_updater; 77 | QString _filterString; 78 | bool _loading; 79 | bool _showNoChanges; 80 | int _migrationMax; 81 | 82 | QPointer _currentDetails; 83 | 84 | AnimeInfo infoFromId(int id) const; 85 | }; 86 | 87 | #endif // MAINVIEWMODEL_H 88 | -------------------------------------------------------------------------------- /lib/animeinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef ANIMEINFO_H 2 | #define ANIMEINFO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "lib_anirem_global.h" 11 | #include "syncedsettings.h" 12 | 13 | class AnimeInfoData; 14 | class LIB_ANIREM_EXPORT AnimeInfo 15 | { 16 | Q_GADGET 17 | Q_DECLARE_TR_FUNCTIONS(AnimeInfo) 18 | 19 | Q_PROPERTY(int id READ id WRITE setId USER true) 20 | Q_PROPERTY(QString title READ title WRITE setTitle) 21 | Q_PROPERTY(AnimeInfo::SeasonMap seasonState READ seasonState WRITE setSeasonState) 22 | Q_PROPERTY(int totalSeasonCount READ totalSeasonCount STORED false) 23 | Q_PROPERTY(bool hasNewSeasons READ hasNewSeasons STORED false) 24 | Q_PROPERTY(QDateTime lastUpdateCheck READ lastUpdateCheck WRITE setLastUpdateCheck) 25 | Q_PROPERTY(QUrl relationsUrl READ relationsUrl CONSTANT STORED false) 26 | 27 | public: 28 | enum SeasonType { 29 | Anime, 30 | Ova, 31 | Movie, 32 | Hentai, 33 | Manga, 34 | Oneshot, 35 | Doujin, 36 | Hmanga, 37 | Unknown 38 | }; 39 | Q_ENUM(SeasonType) 40 | 41 | using SeasonInfo = QPair; //(count, changed) 42 | using SeasonMap = QMap; 43 | 44 | Q_INVOKABLE AnimeInfo(); 45 | AnimeInfo(int id, const QString &title); 46 | AnimeInfo(const AnimeInfo &other); 47 | AnimeInfo &operator=(const AnimeInfo &other); 48 | ~AnimeInfo(); 49 | 50 | operator bool() const; 51 | bool operator!() const; 52 | 53 | int id() const; 54 | QString title() const; 55 | SeasonMap seasonState() const; 56 | SeasonInfo seasonInfo(SeasonType type) const; 57 | int totalSeasonCount() const; 58 | bool hasNewSeasons() const; 59 | QDateTime lastUpdateCheck() const; 60 | QUrl relationsUrl() const; 61 | 62 | void setSeasonState(const SeasonMap &seasonState); 63 | void setLastUpdateCheck(QDateTime lastUpdateCheck); 64 | 65 | Q_INVOKABLE void setAllUnchanged(); 66 | Q_INVOKABLE void setSeasonInfo(SeasonType type, SeasonInfo info); 67 | 68 | Q_INVOKABLE static QString typeToString(SeasonType type); 69 | 70 | bool operator==(const AnimeInfo &other) const; 71 | bool operator!=(const AnimeInfo &other) const; 72 | 73 | static std::tuple apiMediumToType(const QString &medium, SyncedSettings *settings); 74 | 75 | private: 76 | QSharedDataPointer d; 77 | 78 | void setId(int id); 79 | void setTitle(const QString &title); 80 | }; 81 | 82 | Q_DECLARE_METATYPE(AnimeInfo::SeasonInfo) 83 | Q_DECLARE_METATYPE(AnimeInfo::SeasonMap) 84 | 85 | #endif // ANIMEINFO_H 86 | -------------------------------------------------------------------------------- /gui/widgets/widgetsupdatenotifier.cpp: -------------------------------------------------------------------------------- 1 | #include "widgetsupdatenotifier.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "instancesetup.h" 7 | 8 | WidgetsUpdateNotifier::WidgetsUpdateNotifier(QObject *parent) : 9 | QObject(parent), 10 | _trayIcon(new QSystemTrayIcon(QApplication::windowIcon(), this)), 11 | _trayMenu(new QMenu()), 12 | _blinkTimer(new QTimer(this)), 13 | _normalIcon(QStringLiteral(":/icons/main.ico")), 14 | _invertIcon(QStringLiteral(":/icons/inverted.ico")), 15 | _currentNormal(true) 16 | { 17 | _trayMenu->addAction(QIcon::fromTheme(QStringLiteral("gtk-execute")), tr("Show Main Window"), 18 | this, &WidgetsUpdateNotifier::showMainViewModel); 19 | _trayMenu->addAction(QIcon::fromTheme(QStringLiteral("gtk-quit")), tr("Quit"), 20 | this, &WidgetsUpdateNotifier::quit); 21 | _trayIcon->setContextMenu(_trayMenu.data()); 22 | 23 | _trayIcon->setToolTip(QApplication::applicationDisplayName()); 24 | connect(_trayIcon, &QSystemTrayIcon::activated, 25 | this, &WidgetsUpdateNotifier::trayActivated); 26 | connect(_trayIcon, &QSystemTrayIcon::messageClicked, 27 | this, &WidgetsUpdateNotifier::showMainViewModel); 28 | 29 | _blinkTimer->setInterval(500); 30 | connect(_blinkTimer, &QTimer::timeout, 31 | this, &WidgetsUpdateNotifier::timerTriggered); 32 | } 33 | 34 | void WidgetsUpdateNotifier::showNotification(bool isPositive, const QString &title, const QString &description) 35 | { 36 | _trayIcon->show(); 37 | _blinkTimer->start(); 38 | _trayIcon->showMessage(title, description, isPositive ? QSystemTrayIcon::Information : QSystemTrayIcon::Critical); 39 | } 40 | 41 | void WidgetsUpdateNotifier::updateProgress(int value, int max) 42 | { 43 | Q_UNUSED(value) 44 | Q_UNUSED(max) 45 | } 46 | 47 | void WidgetsUpdateNotifier::timerTriggered() 48 | { 49 | if(_currentNormal) 50 | _trayIcon->setIcon(_invertIcon); 51 | else 52 | _trayIcon->setIcon(_normalIcon); 53 | _currentNormal = !_currentNormal; 54 | if(getMainWindow()) { 55 | _trayIcon->hide(); 56 | _blinkTimer->stop(); 57 | } 58 | } 59 | 60 | void WidgetsUpdateNotifier::trayActivated(QSystemTrayIcon::ActivationReason reason) 61 | { 62 | switch (reason) { 63 | case QSystemTrayIcon::DoubleClick: 64 | case QSystemTrayIcon::Trigger: 65 | showMainViewModel(); 66 | break; 67 | default: 68 | break; 69 | } 70 | } 71 | 72 | void WidgetsUpdateNotifier::showMainViewModel() 73 | { 74 | _trayIcon->hide(); 75 | _blinkTimer->stop(); 76 | coreApp->show(); 77 | } 78 | 79 | void WidgetsUpdateNotifier::quit() 80 | { 81 | _trayIcon->hide(); 82 | _blinkTimer->stop(); 83 | qApp->quit(); 84 | } 85 | -------------------------------------------------------------------------------- /gui/quick/DetailsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Layouts 1.3 4 | import de.skycoder42.QtMvvm.Core 1.0 5 | import de.skycoder42.QtMvvm.Quick 1.0 6 | import de.skycoder42.anirem 1.0 7 | 8 | Page { 9 | id: detailsView 10 | property DetailsViewModel viewModel: null 11 | 12 | header: ContrastToolBar { 13 | id: toolbar 14 | RowLayout { 15 | anchors.fill: parent 16 | spacing: 0 17 | 18 | ActionButton { 19 | text: qsTr("Go back") 20 | icon.name: "arrow-left" 21 | icon.source: "qrc:/icons/ic_arrow_back.svg" 22 | onClicked: QuickPresenter.popView(); 23 | } 24 | 25 | ToolBarLabel { 26 | text: qsTr("%1 Details").arg(viewModel.animeInfo ? viewModel.animeInfo.title : qsTr("Anime")) 27 | Layout.fillWidth: true 28 | } 29 | 30 | ActionButton { 31 | id: unmarkAction 32 | icon.name: "dialog-ok-applyk" 33 | icon.source: "qrc:/icons/ic_check.svg" 34 | text: qsTr("Unmark new seasons") 35 | onClicked: viewModel.uncheckAnime() 36 | visible: viewModel.animeInfo ? viewModel.animeInfo.hasNewSeasons : false 37 | } 38 | 39 | ActionButton { 40 | id: openInBrowser 41 | icon.name: "internet-web-browser" 42 | icon.source: "qrc:/icons/ic_open_in_browser.svg" 43 | text: qsTr("Open relations in your browser") 44 | onClicked: Qt.openUrlExternally(viewModel.animeInfo.relationsUrl) 45 | } 46 | } 47 | } 48 | 49 | PresenterProgress { 50 | z: 10 51 | } 52 | 53 | ScrollView { 54 | id: content 55 | anchors.fill: parent 56 | contentHeight: mainLayout.height 57 | contentWidth: width 58 | clip: true 59 | 60 | ColumnLayout { 61 | id: mainLayout 62 | anchors.left: parent.left 63 | anchors.right: parent.right 64 | 65 | Image { 66 | id: previewImage 67 | asynchronous: true 68 | fillMode: Image.PreserveAspectFit 69 | horizontalAlignment: Image.AlignHCenter 70 | verticalAlignment: Image.AlignVCenter 71 | Layout.minimumWidth: 200 72 | Layout.maximumWidth: 200 73 | Layout.minimumHeight: 277 74 | Layout.maximumHeight: 277 75 | Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 76 | source: viewModel && viewModel.animeInfo ? "image://proxer/%1".arg(viewModel.animeInfo.id) : "" 77 | mipmap: true 78 | } 79 | 80 | Label { 81 | id: detailsLabel 82 | Layout.preferredWidth: implicitWidth + 16 83 | Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 84 | wrapMode: Text.Wrap 85 | textFormat: Text.RichText 86 | text: viewModel ? viewModel.detailsText : "" 87 | horizontalAlignment: Text.AlignHCenter 88 | verticalAlignment: Text.AlignVCenter 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /gui/quick/AddAnimeDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Layouts 1.3 4 | import de.skycoder42.QtMvvm.Core 1.0 5 | import de.skycoder42.QtMvvm.Quick 1.0 6 | import de.skycoder42.anirem 1.0 7 | 8 | AlertDialog { 9 | id: addAnimeDialog 10 | title: qsTr("Add Anime") 11 | property AddAnimeViewModel viewModel: null 12 | 13 | Connections { 14 | target: viewModel 15 | onClose: QuickPresenter.popView() 16 | } 17 | 18 | GridLayout { 19 | columns: 2 20 | columnSpacing: 20 21 | width: parent.width 22 | 23 | Label { 24 | text: qsTr("Proxer-ID:") 25 | Layout.fillWidth: true 26 | color: idEdit.focus ? idEdit.selectionColor : idEdit.color 27 | opacity: idEdit.focus ? 1 : 0.5 28 | } 29 | 30 | Item { 31 | id: previewItem 32 | Layout.rowSpan: 3 33 | Layout.preferredHeight: 100 34 | Layout.preferredWidth: 66 35 | 36 | readonly property bool showLoading: viewModel.loading || previewImage.status == Image.Loading 37 | 38 | Image { 39 | id: previewImage 40 | anchors.fill: parent 41 | asynchronous: true 42 | fillMode: Image.PreserveAspectFit 43 | horizontalAlignment: Image.AlignHCenter 44 | verticalAlignment: Image.AlignVCenter 45 | source: viewModel.id > 0 ? "image://proxer/%1".arg(viewModel.id) : "" 46 | mipmap: true 47 | visible: !previewItem.showLoading 48 | } 49 | 50 | BusyIndicator { 51 | anchors.fill: parent 52 | visible: previewItem.showLoading 53 | } 54 | } 55 | 56 | TextField { 57 | id: idEdit 58 | Layout.fillWidth: true 59 | placeholderText: "894" 60 | focus: true 61 | selectByMouse: true 62 | validator: IntValidator { 63 | bottom: 1 64 | top: 2147483647 65 | } 66 | 67 | text: viewModel.id > 0 ? viewModel.id : "" 68 | onEditingFinished: viewModel.id = text; 69 | } 70 | 71 | Label { 72 | text: qsTr("Title:") 73 | Layout.fillWidth: true 74 | color: titleField.focus ? idEdit.selectionColor : idEdit.color 75 | opacity: titleField.focus ? 1 : 0.5 76 | } 77 | 78 | ComboBox { 79 | id: titleField 80 | Layout.fillWidth: true 81 | Layout.columnSpan: 2 82 | editable: false 83 | model: viewModel.nameModel 84 | textRole: "display" 85 | onCurrentTextChanged: viewModel.title = currentText 86 | 87 | Connections { 88 | target: viewModel 89 | onTitleChanged: titleField.currentIndex = titleField.find(title) 90 | } 91 | } 92 | } 93 | 94 | footer: DialogButtonBox { 95 | id: btnBox 96 | standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel 97 | 98 | Component.onCompleted: { 99 | var btn = standardButton(DialogButtonBox.Ok); 100 | btn.enabled = Qt.binding(function() { return viewModel.acceptable; }); 101 | } 102 | } 103 | 104 | onAccepted: viewModel.accept(); 105 | } 106 | -------------------------------------------------------------------------------- /lib/libanirem.cpp: -------------------------------------------------------------------------------- 1 | #include "libanirem.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "jsonseasondataconverter.h" 12 | #include "animeinfo.h" 13 | #include "proxerapi.h" 14 | #include "syncedsettings.h" 15 | #include "iupdatenotifier.h" 16 | #include "seasonstatusloader.h" 17 | #include "storedcookiejar.h" 18 | 19 | void AniRem::prepareTranslations() 20 | { 21 | auto translator = new QTranslator(qApp); 22 | if(translator->load(QLocale(), 23 | QStringLiteral("anirem"), 24 | QStringLiteral("_"), 25 | QLibraryInfo::location(QLibraryInfo::TranslationsPath))) 26 | qApp->installTranslator(translator); 27 | } 28 | 29 | void AniRem::setup(QtDataSync::Setup &setup, bool passive) 30 | { 31 | setup.setRemoteConfiguration({QStringLiteral("wss://apps.skycoder42.de/datasync/")}) 32 | .serializer()->addJsonTypeConverter(); 33 | if(passive) 34 | setup.setRemoteObjectHost(QStringLiteral("local:de.skycoder42.anirem.daemon")); 35 | 36 | //also: setup API 37 | ProxerApi api; 38 | api.restClient()->serializer()->setAllowDefaultNull(true); 39 | api.restClient()->manager()->setCookieJar(new StoredCookieJar()); 40 | if(LocalSettings::instance()->account.token.isSet()) 41 | api.restClient()->addGlobalHeader("proxer-api-token", LocalSettings::instance()->account.token.get().toUtf8()); 42 | } 43 | 44 | void AniRem::setProxerToken(const QString &token, LocalSettings *settings) 45 | { 46 | if(token.isEmpty()) { 47 | settings->account.token.reset(); 48 | ProxerApi api; 49 | api.restClient()->removeGlobalHeader("proxer-api-token"); 50 | } else { 51 | settings->account.token = token; 52 | ProxerApi api; 53 | api.restClient()->addGlobalHeader("proxer-api-token", token.toUtf8()); 54 | } 55 | } 56 | 57 | 58 | 59 | namespace { 60 | 61 | void cleanSettings() 62 | { 63 | try { 64 | //WORKAROUND for settings destruction bug 65 | auto accessor = dynamic_cast(SyncedSettings::instance()->accessor()); 66 | if(accessor) 67 | delete accessor; 68 | qDebug() << "Cleaned settings"; 69 | } catch(QException &e) { 70 | qCritical() << "Failed to clean settings:" << e.what(); 71 | } 72 | 73 | try { 74 | //WORKAROUND for settings destruction bug 75 | auto loader = QtMvvm::ServiceRegistry::instance()->service(); 76 | if(loader) 77 | loader->preClean(); 78 | qDebug() << "Cleaned status loader"; 79 | } catch(QException &e) { 80 | qCritical() << "Failed to status loader:" << e.what(); 81 | } 82 | } 83 | 84 | void setupAniRemLib() 85 | { 86 | qRegisterMetaType("AnimeInfo::SeasonMap"); 87 | 88 | QJsonSerializer::registerListConverters(); 89 | 90 | QtMvvm::registerInterfaceConverter(); 91 | QtMvvm::ServiceRegistry::instance()->registerObject(); 92 | 93 | qAddPostRoutine(cleanSettings); 94 | } 95 | 96 | } 97 | Q_COREAPP_STARTUP_FUNCTION(setupAniRemLib) 98 | -------------------------------------------------------------------------------- /gui/core/detailsviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "detailsviewmodel.h" 2 | #include 3 | 4 | const QString DetailsViewModel::ParamInfo = QStringLiteral("info"); 5 | const QString DetailsViewModel::ParamOwner = QStringLiteral("owner"); 6 | 7 | QVariantHash DetailsViewModel::params(const AnimeInfo &info, QtMvvm::ViewModel *owner) 8 | { 9 | return { 10 | {ParamInfo, QVariant::fromValue(info)}, 11 | {ParamOwner, QVariant::fromValue(owner)} 12 | }; 13 | } 14 | 15 | DetailsViewModel::DetailsViewModel(QObject *parent) : 16 | ViewModel(parent), 17 | _store(new AniremStore(this)), 18 | _loader(nullptr), 19 | _animeInfo() 20 | { 21 | connect(_store, &AniremStore::dataChanged, 22 | this, &DetailsViewModel::dataChanged); 23 | } 24 | 25 | AnimeInfo DetailsViewModel::animeInfo() const 26 | { 27 | return _animeInfo; 28 | } 29 | 30 | QString DetailsViewModel::detailsText() const 31 | { 32 | auto state = _animeInfo.seasonState(); 33 | if(state.isEmpty()) { 34 | return tr("Anime has no connections…"); 35 | } else { 36 | auto string = QStringLiteral(""); 37 | for(auto it = state.constBegin(); it != state.constEnd(); it++) { 38 | auto row = tr(""); 39 | if(it->second) 40 | row = row.arg(tr("%L1 (new)")); 41 | string += row.arg(it->first) 42 | .arg(AnimeInfo::typeToString(it.key())); 43 | } 44 | return string + QStringLiteral("
 %2:  %L1 
"); 45 | } 46 | } 47 | 48 | ImageLoader *DetailsViewModel::imageLoader() const 49 | { 50 | return _loader; 51 | } 52 | 53 | void DetailsViewModel::setAnimeInfo(const AnimeInfo &animeInfo) 54 | { 55 | if (_animeInfo == animeInfo) 56 | return; 57 | 58 | _animeInfo = animeInfo; 59 | emit animeInfoChanged(); 60 | } 61 | 62 | void DetailsViewModel::clear() 63 | { 64 | _animeInfo = {}; 65 | emit animeInfoChanged(); 66 | } 67 | 68 | void DetailsViewModel::uncheckAnime() 69 | { 70 | if(_animeInfo && _animeInfo.hasNewSeasons()) { 71 | auto oldInfo = _animeInfo; 72 | try { 73 | _animeInfo.setAllUnchanged(); 74 | _store->save(_animeInfo); 75 | emit animeInfoChanged(); 76 | } catch(QException &e) { 77 | _animeInfo = oldInfo; 78 | qCritical() << "Failed to save entry with id" << _animeInfo.id() 79 | << "and error:" << e.what(); 80 | QtMvvm::critical(tr("Adding failed!"), 81 | tr("Failed to add anime to underlying data storage.")); 82 | } 83 | } 84 | } 85 | 86 | void DetailsViewModel::dataChanged(const QString &key, const QVariant &value) 87 | { 88 | if(_animeInfo.id() == AniremStore::toKey(key)) { 89 | _animeInfo = value.value(); 90 | emit animeInfoChanged(); 91 | } 92 | } 93 | 94 | void DetailsViewModel::onInit(const QVariantHash ¶ms) 95 | { 96 | if(params.contains(ParamInfo)) 97 | setAnimeInfo(params.value(ParamInfo).value()); 98 | auto owner = params.value(ParamOwner).value(); 99 | if(owner) { 100 | QMetaObject::invokeMethod(owner, "setDetailsView", 101 | Q_ARG(QPointer, this)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/lib.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT = core datasync restclient mvvmcore 4 | win32: CONFIG += skip_target_version_ext 5 | CONFIG += lib_bundle 6 | 7 | TARGET = anirem 8 | 9 | DEFINES += BUILD_LIB_ANIREM 10 | 11 | PUBLIC_HEADERS = \ 12 | $$PWD/lib_anirem_global.h \ 13 | $$PWD/libanirem.h \ 14 | $$PWD/apihelper.h \ 15 | $$PWD/seasonstatusloader.h \ 16 | $$PWD/iupdatenotifier.h \ 17 | $$PWD/animeinfo.h \ 18 | $$PWD/passiveupdater.h 19 | 20 | HEADERS += $$PUBLIC_HEADERS \ 21 | jsonseasondataconverter.h \ 22 | storedcookiejar.h 23 | 24 | SOURCES += \ 25 | libanirem.cpp \ 26 | apihelper.cpp \ 27 | animeinfo.cpp \ 28 | jsonseasondataconverter.cpp \ 29 | seasonstatusloader.cpp \ 30 | passiveupdater.cpp \ 31 | storedcookiejar.cpp 32 | 33 | REST_API_FILES += api/proxerstatus.xml \ 34 | api/proxerentryvalue.xml \ 35 | api/proxerentry.xml \ 36 | api/proxerrelations.xml \ 37 | api/proxernamevalue.xml \ 38 | api/proxernames.xml \ 39 | api/proxerlistvalue.xml \ 40 | api/proxerlist.xml \ 41 | api/proxerlogin.xml \ 42 | api/proxerloginvalue.xml \ 43 | api/proxerapi.xml \ 44 | api/infoclass.xml \ 45 | api/userclass.xml 46 | 47 | SETTINGS_GENERATORS += \ 48 | localsettings.xml \ 49 | syncedsettings.xml 50 | 51 | MVVM_SETTINGS_FILES += $$PWD/../gui/core/settings.xml 52 | 53 | TRANSLATIONS += anirem_lib_de.ts 54 | 55 | DISTFILES += $$TRANSLATIONS 56 | 57 | win32:CONFIG(release, debug|release): OUT_INC_DIR += $$OUT_PWD/release 58 | else:win32:CONFIG(debug, debug|release): OUT_INC_DIR += $$OUT_PWD/debug 59 | else: OUT_INC_DIR = $$OUT_PWD 60 | INCLUDEPATH += $$OUT_INC_DIR 61 | 62 | !ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) 63 | else: include($$OUT_PWD/qpmx_generated.pri) 64 | 65 | # install copy 66 | system($$QMAKE_MKDIR $$shell_quote($$shell_path($$shadowed($$SETTINGSGENERATOR_DIR)))) 67 | for(header, SETTINGSGENERATOR_BUILD_HEADERS) { 68 | theader = $$shadowed($$SETTINGSGENERATOR_DIR/$$basename(header)) 69 | !exists($$theader):system($$QMAKE_COPY_FILE $$shell_quote($$shell_path($$header)) $$shell_quote($$shell_path($$theader))) 70 | } 71 | 72 | # install 73 | target.path = $$INSTALL_LIBS 74 | qpmx_ts_target.path = $$INSTALL_TRANSLATIONS 75 | header_install.files = $$PUBLIC_HEADERS \ 76 | $$SETTINGSGENERATOR_BUILD_HEADERS \ 77 | $$OUT_INC_DIR/localsettings.h \ 78 | $$OUT_INC_DIR/syncedsettings.h \ 79 | $$OUT_INC_DIR/proxerstatus.h \ 80 | $$OUT_INC_DIR/proxerentryvalue.h \ 81 | $$OUT_INC_DIR/proxerentry.h \ 82 | $$OUT_INC_DIR/proxerrelations.h \ 83 | $$OUT_INC_DIR/infoclass.h \ 84 | $$OUT_INC_DIR/proxerapi.h 85 | header_install.CONFIG += no_check_exist 86 | header_install.path = $$INSTALL_HEADERS/anirem 87 | INSTALLS += target qpmx_ts_target 88 | !android:!mac: INSTALLS += header_install 89 | 90 | # install osx headers 91 | FRAMEWORK_HEADERS.version = Versions 92 | for(hfile, header_install.files): FRAMEWORK_HEADERS.files += $$relative_path($$hfile, $$OUT_PWD) 93 | FRAMEWORK_HEADERS.path = Headers 94 | QMAKE_BUNDLE_DATA += FRAMEWORK_HEADERS 95 | 96 | QMAKE_EXTRA_TARGETS += qtifw 97 | -------------------------------------------------------------------------------- /gui/quick/LoginDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Controls 2.3 3 | import QtQuick.Layouts 1.3 4 | import de.skycoder42.QtMvvm.Core 1.0 5 | import de.skycoder42.QtMvvm.Quick 1.0 6 | import de.skycoder42.anirem 1.0 7 | 8 | AlertDialog { 9 | id: loginDialog 10 | title: qsTr("Login") 11 | property LoginViewModel viewModel: null 12 | 13 | Connections { 14 | target: viewModel 15 | onLoginSuccessful: QuickPresenter.popView() 16 | } 17 | 18 | Item { 19 | id: content 20 | anchors.fill: parent 21 | implicitWidth: contentLayout.implicitWidth 22 | implicitHeight: contentLayout.implicitHeight 23 | 24 | ColumnLayout { 25 | id: contentLayout 26 | anchors.fill: parent 27 | enabled: !viewModel.loading 28 | 29 | Label { 30 | text: qsTr("Username:") 31 | Layout.fillWidth: true 32 | color: userNameEdit.focus ? userNameEdit.selectionColor : userNameEdit.color 33 | opacity: userNameEdit.focus ? 1 : 0.5 34 | } 35 | 36 | TextField { 37 | id: userNameEdit 38 | Layout.fillWidth: true 39 | focus: true 40 | selectByMouse: true 41 | 42 | MvvmBinding { 43 | viewModel: loginDialog.viewModel 44 | viewModelProperty: "userName" 45 | view: userNameEdit 46 | viewProperty: "text" 47 | viewChangeSignal: "editingFinished()" 48 | } 49 | } 50 | 51 | Label { 52 | text: qsTr("Password:") 53 | Layout.fillWidth: true 54 | color: passwordEdit.focus ? passwordEdit.selectionColor : userNameEdit.color 55 | opacity: passwordEdit.focus ? 1 : 0.5 56 | } 57 | 58 | TextField { 59 | id: passwordEdit 60 | Layout.fillWidth: true 61 | selectByMouse: true 62 | echoMode: TextInput.Password 63 | 64 | MvvmBinding { 65 | viewModel: loginDialog.viewModel 66 | viewModelProperty: "password" 67 | view: passwordEdit 68 | viewProperty: "text" 69 | viewChangeSignal: "editingFinished()" 70 | } 71 | } 72 | 73 | Label { 74 | text: qsTr("Authenticaton Code (optional):") 75 | Layout.fillWidth: true 76 | color: authEdit.focus ? authEdit.selectionColor : userNameEdit.color 77 | opacity: authEdit.focus ? 1 : 0.5 78 | } 79 | 80 | TextField { 81 | id: authEdit 82 | Layout.fillWidth: true 83 | selectByMouse: true 84 | validator: IntValidator { 85 | bottom: 0 86 | top: 999999 87 | } 88 | 89 | MvvmBinding { 90 | viewModel: loginDialog.viewModel 91 | viewModelProperty: "authCode" 92 | view: authEdit 93 | viewProperty: "text" 94 | viewChangeSignal: "editingFinished()" 95 | } 96 | } 97 | 98 | //FEATURE disable for now 99 | // Switch { 100 | // id: storePwSwitch 101 | // text: qsTr("Remember my password") 102 | 103 | // MvvmBinding { 104 | // viewModel: loginDialog.viewModel 105 | // viewModelProperty: "storePassword" 106 | // view: storePwSwitch 107 | // viewProperty: "checked" 108 | // } 109 | // } 110 | } 111 | 112 | BusyIndicator { 113 | anchors.centerIn: parent 114 | z: 50 115 | visible: viewModel.loading 116 | } 117 | } 118 | 119 | footer: DialogButtonBox { 120 | id: btnBox 121 | standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel 122 | enabled: !viewModel.loading 123 | } 124 | 125 | onAccepted: { 126 | viewModel.login(); 127 | loginDialog.visible = true; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /gui/quick/quick.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += quick mvvmquick mvvmdatasyncquick concurrent 4 | android: QT += androidextras 5 | 6 | android: TARGET = anirem-activity 7 | else: TARGET = anirem 8 | 9 | HEADERS += \ 10 | proxerimageprovider.h \ 11 | qmltoast.h 12 | 13 | SOURCES += main.cpp \ 14 | proxerimageprovider.cpp \ 15 | qmltoast.cpp 16 | 17 | RESOURCES += \ 18 | anirem-quick.qrc 19 | 20 | TRANSLATIONS += anirem_quick_de.ts 21 | 22 | EXTRA_TRANSLATIONS += anirem_de.ts 23 | 24 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android 25 | 26 | DISTFILES += $$TRANSLATIONS \ 27 | $$EXTRA_TRANSLATIONS \ 28 | android/AndroidManifest.xml \ 29 | android/build.gradle \ 30 | android/src/de/skycoder42/anirem/* \ 31 | android/res/values/* \ 32 | android/res/values-de/* \ 33 | android/res/xml/* \ 34 | android/res/drawable-hdpi/* \ 35 | android/res/drawable-mdpi/* \ 36 | android/res/drawable-xhdpi/* \ 37 | android/res/drawable-xxhdpi/* \ 38 | android/res/drawable-xxxhdpi/* \ 39 | android/res/mipmap-hdpi/* \ 40 | android/res/mipmap-mdpi/* \ 41 | android/res/mipmap-xhdpi/* \ 42 | android/res/mipmap-xxhdpi/* \ 43 | android/res/mipmap-xxxhdpi/* \ 44 | android/res/mipmap-anydpi-v26/* \ 45 | android/src/de/skycoder42/anirem/Globals.java 46 | 47 | # install 48 | target.path = $$INSTALL_BINS 49 | qpmx_ts_target.path = $$INSTALL_TRANSLATIONS 50 | extra_ts_target.path = $$INSTALL_TRANSLATIONS 51 | INSTALLS += target qpmx_ts_target extra_ts_target 52 | 53 | # Link with core project 54 | win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lanirem-core 55 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lanirem-core 56 | else:unix: LIBS += -L$$OUT_PWD/../core/ -lanirem-core 57 | 58 | INCLUDEPATH += $$PWD/../core 59 | DEPENDPATH += $$PWD/../core 60 | 61 | win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libanirem-core.a 62 | else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libanirem-core.a 63 | else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/anirem-core.lib 64 | else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/anirem-core.lib 65 | else:unix: PRE_TARGETDEPS += $$OUT_PWD/../core/libanirem-core.a 66 | 67 | include(../../lib.pri) 68 | 69 | android { 70 | LIBS += -L$$PWD/openssl/openssl -lcrypto -lssl 71 | ANDROID_EXTRA_LIBS += \ 72 | $$PWD/openssl/openssl/libcrypto.so \ 73 | $$PWD/openssl/openssl/libssl.so 74 | RESOURCES += anirem-android.qrc 75 | 76 | tsqtInstall.path = $$INSTALL_TRANSLATIONS 77 | tsqtInstall.files = \ 78 | $$[QT_INSTALL_TRANSLATIONS]/qtbase_*.qm \ 79 | $$[QT_INSTALL_TRANSLATIONS]/qtdeclarative_*.qm \ 80 | $$[QT_INSTALL_TRANSLATIONS]/qtquickcontrols_*.qm \ 81 | $$[QT_INSTALL_TRANSLATIONS]/qtwebsockets_*.qm \ 82 | $$[QT_INSTALL_TRANSLATIONS]/qtdatasync_*.qm \ 83 | $$[QT_INSTALL_TRANSLATIONS]/qtmvvmcore_*.qm \ 84 | $$[QT_INSTALL_TRANSLATIONS]/qtmvvmdatasynccore_*.qm \ 85 | $$[QT_INSTALL_TRANSLATIONS]/qtmvvmquick_*.qm \ 86 | $$[QT_INSTALL_TRANSLATIONS]/qtmvvmdatasyncquick_*.qm 87 | INSTALLS += tsqtInstall 88 | } 89 | 90 | !ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) 91 | else: include($$OUT_PWD/qpmx_generated.pri) 92 | 93 | QMAKE_EXTRA_TARGETS += qtifw 94 | -------------------------------------------------------------------------------- /gui/quick/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "proxerimageprovider.h" 13 | #include "qmltoast.h" 14 | 15 | #ifdef Q_OS_ANDROID 16 | #include 17 | #endif 18 | 19 | QTMVVM_REGISTER_CORE_APP(AniRemApp) 20 | 21 | namespace { 22 | 23 | void setStatusBarColor(QColor color) 24 | { 25 | #ifdef Q_OS_ANDROID 26 | if(QtAndroid::androidSdkVersion() >= 21) { 27 | auto activity = QtAndroid::androidActivity(); 28 | QtAndroid::runOnAndroidThreadSync([=](){ 29 | const auto FLAG_TRANSLUCENT_STATUS = QAndroidJniObject::getStaticField("android/view/WindowManager$LayoutParams", 30 | "FLAG_TRANSLUCENT_STATUS"); 31 | const auto FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = QAndroidJniObject::getStaticField("android/view/WindowManager$LayoutParams", 32 | "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS"); 33 | const auto jColor = QAndroidJniObject::callStaticMethod("android/graphics/Color", 34 | "parseColor", 35 | "(Ljava/lang/String;)I", 36 | QAndroidJniObject::fromString(color.name()).object()); 37 | 38 | QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); 39 | if(window.isValid()) { 40 | window.callMethod("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS); 41 | window.callMethod("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 42 | window.callMethod("setStatusBarColor", "(I)V", jColor); 43 | } 44 | }); 45 | } 46 | #else 47 | Q_UNUSED(color); 48 | #endif 49 | } 50 | 51 | QObject *createQmlToast(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 52 | { 53 | Q_UNUSED(jsEngine) 54 | return new QmlToast(qmlEngine); 55 | } 56 | 57 | } 58 | 59 | int main(int argc, char *argv[]) 60 | { 61 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 62 | setStatusBarColor(QColor(0x4E, 0x4E, 0x4E)); 63 | // If you want to support file dialogs on platforms other then android, use a QApplication instead (and add QT += widgets to the pro file) 64 | QGuiApplication app(argc, argv); 65 | 66 | qmlRegisterSingletonType("de.skycoder42.anirem", 1, 0, "QmlToast", createQmlToast); 67 | 68 | QtMvvm::registerDataSyncQuick(); 69 | qmlRegisterUncreatableType("de.skycoder42.anirem", 1, 0, "MainViewModel", QStringLiteral("ViewModels cannot be created!")); 70 | qmlRegisterUncreatableType("de.skycoder42.anirem", 1, 0, "AddAnimeViewModel", QStringLiteral("ViewModels cannot be created!")); 71 | qmlRegisterUncreatableType("de.skycoder42.anirem", 1, 0, "DetailsViewModel", QStringLiteral("ViewModels cannot be created!")); 72 | qmlRegisterUncreatableType("de.skycoder42.anirem", 1, 0, "EntryViewModel", QStringLiteral("ViewModels cannot be created!")); 73 | qmlRegisterUncreatableType("de.skycoder42.anirem", 1, 0, "LoginViewModel", QStringLiteral("ViewModels cannot be created!")); 74 | 75 | QQmlApplicationEngine engine; 76 | engine.load(QUrl(QStringLiteral("qrc:/qml/App.qml"))); 77 | engine.addImageProvider(QStringLiteral("proxer"), new ProxerImageProvider()); 78 | if (engine.rootObjects().isEmpty()) 79 | return -1; 80 | 81 | return app.exec(); 82 | } 83 | -------------------------------------------------------------------------------- /gui/core/imageloader.cpp: -------------------------------------------------------------------------------- 1 | #include "imageloader.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const QString ImageLoader::CacheDirName = QStringLiteral("preview-images"); 9 | const QString ImageLoader::ImageNameTemplate = QStringLiteral("img_%1.png"); 10 | 11 | ImageLoader::ImageLoader(QObject *parent) : 12 | QObject(parent), 13 | _nam(nullptr), 14 | _cache() 15 | {} 16 | 17 | void ImageLoader::loadImage(int id) 18 | { 19 | QMetaObject::invokeMethod(this, "loadImageImpl", Qt::QueuedConnection, 20 | Q_ARG(int, id)); 21 | } 22 | 23 | void ImageLoader::clearCache() 24 | { 25 | QMetaObject::invokeMethod(this, "clearCacheImpl", Qt::QueuedConnection); 26 | } 27 | 28 | void ImageLoader::loadImageImpl(int id) 29 | { 30 | // check if cached 31 | auto imagePtr = _cache.object(id); 32 | if(imagePtr) { 33 | emit imageLoaded(id, *imagePtr); 34 | return; 35 | } 36 | 37 | // check if in cache dir 38 | QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); 39 | if(cacheDir.mkpath(CacheDirName) && cacheDir.cd(CacheDirName)) { 40 | auto imgPath = cacheDir.absoluteFilePath(ImageNameTemplate.arg(id)); 41 | if(QFile::exists(imgPath)) { 42 | QImage image(imgPath, "png"); 43 | if(!image.isNull()) { 44 | _cache.insert(id, new QImage(image)); 45 | emit imageLoaded(id, image); 46 | return; 47 | } 48 | } 49 | } else 50 | qWarning() << "Cache directory is not accessible"; 51 | 52 | 53 | if(!_nam) 54 | _nam = new QNetworkAccessManager(this); 55 | auto reply = _nam->get(QNetworkRequest(QStringLiteral("https://cdn.proxer.me/cover/%1.jpg").arg(id))); 56 | connect(reply, &QNetworkReply::finished, this, [=](){ 57 | imageNetworkReply(id, reply); 58 | }, Qt::QueuedConnection); 59 | } 60 | 61 | void ImageLoader::clearCacheImpl() 62 | { 63 | _cache.clear(); 64 | 65 | QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); 66 | if(cacheDir.exists(CacheDirName)) { 67 | QString rmName = QStringLiteral(".rm-dir-") + QUuid::createUuid().toString(); 68 | if(cacheDir.rename(CacheDirName, rmName)) { 69 | if(cacheDir.cd(rmName)) { 70 | QtConcurrent::run([cacheDir](){ 71 | auto cDir = cacheDir; 72 | cDir.removeRecursively(); 73 | }); 74 | } 75 | } else if(cacheDir.cd(CacheDirName)) 76 | cacheDir.removeRecursively(); 77 | } 78 | } 79 | 80 | void ImageLoader::imageNetworkReply(int id, QNetworkReply *reply) 81 | { 82 | if(reply->error() == QNetworkReply::NoError) { 83 | QImageReader reader(reply, "jpg"); 84 | QImage image; 85 | if(reader.read(&image)) { 86 | // send result 87 | _cache.insert(id, new QImage(image)); 88 | emit imageLoaded(id, image); 89 | 90 | //save to cache 91 | QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); 92 | if(cacheDir.mkpath(CacheDirName) && cacheDir.cd(CacheDirName)) { 93 | auto imgPath = cacheDir.absoluteFilePath(ImageNameTemplate.arg(id)); 94 | if(!image.save(imgPath, "png")) 95 | qWarning() << "Failed to store image with id" << id << "to cache"; 96 | } else 97 | qWarning() << "Cache directory is not accessible"; 98 | } else 99 | emit imageLoadFailed(id, QStringLiteral("Failed to read image with id %1 with error: %2").arg(id).arg(reader.errorString())); 100 | } else 101 | emit imageLoadFailed(id, QStringLiteral("Network Error for id %1: %2").arg(id).arg(reply->errorString())); 102 | 103 | reply->deleteLater(); 104 | } 105 | -------------------------------------------------------------------------------- /gui/quick/android/src/de/skycoder42/anirem/AniremService.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.anirem; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import android.app.PendingIntent; 7 | import android.app.Notification; 8 | import android.app.NotificationManager; 9 | 10 | import android.graphics.BitmapFactory; 11 | 12 | import android.support.v4.app.NotificationCompat; 13 | import android.support.v4.app.NotificationCompat.BigTextStyle; 14 | 15 | import org.qtproject.qt5.android.bindings.QtService; 16 | 17 | public class AniremService extends QtService { 18 | private NotificationCompat.Builder progressBuilder = null; 19 | 20 | @Override 21 | public void onDestroy() { 22 | super.onDestroy(); 23 | // explicitly exit to prevent the process from beeing cached 24 | System.exit(0); 25 | } 26 | 27 | @Override 28 | public int onStartCommand(Intent intent, int flags, int startId) { 29 | int result = super.onStartCommand(intent, flags, startId); 30 | 31 | if(progressBuilder == null) { 32 | progressBuilder = new NotificationCompat.Builder(this, Globals.ForegroundChannelId) 33 | .setContentTitle(getString(R.string.update_progress_title)) 34 | .setContentText(getString(R.string.update_progress_text)) 35 | .setContentInfo(getString(R.string.app_name)) 36 | .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) 37 | .setSmallIcon(R.drawable.ic_notification) 38 | .setColor(Globals.NormalColor) 39 | .setOngoing(true) 40 | .setProgress(0, 0, true) 41 | .setCategory(NotificationCompat.CATEGORY_PROGRESS); 42 | 43 | if(!Globals.isOreo()) 44 | progressBuilder.setPriority(NotificationCompat.PRIORITY_MIN); 45 | } 46 | 47 | startForeground(Globals.ForegroundId, progressBuilder.build()); 48 | handleStart(); 49 | return result; 50 | } 51 | 52 | public void completeAction() { 53 | stopForeground(true); 54 | stopService(new Intent(this, AniremService.class));//Stop myself 55 | } 56 | 57 | public void updateProgress(int current, int max) 58 | { 59 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 60 | progressBuilder.setProgress(max, current, false) 61 | .setContentText(getString(R.string.update_progress_active_text) + current + " / " + max); 62 | manager.notify(Globals.ForegroundId, progressBuilder.build()); 63 | } 64 | 65 | public void showUpdateNotification(boolean success, String title, String message) { 66 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 67 | 68 | Intent openIntent = new Intent(this, AniremActivity.class); 69 | PendingIntent pending = PendingIntent.getActivity(this, 70 | Globals.OpenIntentId, 71 | openIntent, 72 | PendingIntent.FLAG_UPDATE_CURRENT); 73 | 74 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Globals.NormalChannelId) 75 | .setContentTitle(title) 76 | .setContentText(message) 77 | .setContentInfo(getString(R.string.app_name)) 78 | .setStyle(new NotificationCompat.BigTextStyle() 79 | .bigText(message)) 80 | .setContentIntent(pending) 81 | .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) 82 | .setSmallIcon(R.drawable.ic_notification) 83 | .setColor(Globals.NormalColor) 84 | .setAutoCancel(true) 85 | .setCategory(success ? NotificationCompat.CATEGORY_EVENT : NotificationCompat.CATEGORY_ERROR); 86 | 87 | if(!Globals.isOreo()) 88 | builder.setDefaults(Notification.DEFAULT_ALL); 89 | 90 | manager.notify(Globals.NotifyId, builder.build()); 91 | } 92 | 93 | private static native void handleStart(); 94 | } 95 | -------------------------------------------------------------------------------- /gui/widgets/entrydialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | EntryDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 647 10 | 358 11 | 12 | 13 | 14 | Anime/Manga History 15 | 16 | 17 | true 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | List entries of &type: 29 | 30 | 31 | typeComboBox 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Qt::Horizontal 42 | 43 | 44 | 45 | 40 46 | 20 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | QAbstractItemView::NoEditTriggers 57 | 58 | 59 | true 60 | 61 | 62 | false 63 | 64 | 65 | false 66 | 67 | 68 | true 69 | 70 | 71 | false 72 | 73 | 74 | false 75 | 76 | 77 | 78 | 79 | 80 | 81 | Qt::Horizontal 82 | 83 | 84 | QDialogButtonBox::Close 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | buttonBox 94 | accepted() 95 | EntryDialog 96 | accept() 97 | 98 | 99 | 248 100 | 254 101 | 102 | 103 | 157 104 | 274 105 | 106 | 107 | 108 | 109 | buttonBox 110 | rejected() 111 | EntryDialog 112 | reject() 113 | 114 | 115 | 316 116 | 260 117 | 118 | 119 | 286 120 | 274 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | sudo: required 7 | dist: trusty 8 | osx_image: xcode9.2 9 | 10 | env: 11 | global: 12 | - QPMX_CACHE_DIR=$HOME/.qpmx-cache 13 | - QT_VER=5.10.1 14 | - OPENSSL_VERSION=1.0.2n 15 | - EXTRA_MODULES=".qtremoteobjects .skycoder42 .gcc_64" 16 | - DOCKER_IMAGE=full 17 | - NO_TESTS=true 18 | - ANIREM_VER=2.0.1 19 | - secure: xrjRnCXdhkR0vGe+I7eNRsodmwu59if9H8KMgrRvfNpFSulxTP6gLxV/fOn6tNXHNynKt1wwf6KqRGoDPu8iAaaHyFnXDT0a70TbUH+Sbk+8UuGYaoHT4e+A3ywEhj/u6jX/PpKv40o0iqno2TDXWYAyq63Ik3S6+pu13tLUCOEG+RuS0MGaeKPaZOQAqviGBL3suslYtk2+HCKUpg70S4nuf/fvZbe/3M6KJdGLdBNXj0HxG6h168HObchAyjvGbo2/6GzwRDXbCxLKh0GUl/3kgDSF9/31MoGFEHlZNopTyrmfa6VUgpQ+mdraeqqKv3kwRk/BmwBup7GUL7tCJBrqOfQYdnAaV3YZXJ/a2thechiKZiT4qjuTFBSFXP3hv/wh4ZUpqi0MBUzPe1u8YKbwrfCddeIIzioyP9qVFkXEdAto+WgEGuT58mX83q9fjgRaGMc9x2KzXrqbCH3czdjhoAsg2fTIrAofh3COP2LraeEsy7QN9x4xfl+ySXT+m9WMuj3bR+VMzRQXXc+i5rlH2HGvEBK0nWsvq5LlRiSUMf1vDaoCNIPrcDuvVaIeciAuONHIg4GEbfJbIxvHOWbCXlWVDVhhQqVD8pgZKqVfLjJ0fz+/m/lHfueHKrbts1RXfK+kunQ7thfrjc1s6sNRFdpOZhytZaazTv1Htx0= 20 | - secure: lMC613uPXCZ/acVZBDOS1+9QdNkrB3MbkzlZOelrmR5T24p5DDzG9F1b01zH7QFc1LFcy8TCUR1ZD6a5j1sHi/ALWm2lAhJdbNTKlvtM71oP885gP0+IuHM6b/oRWZ14ER+Cug7zl34GmF76vMfm5QQe3cqQtreITzbf62R7QB5H2FpGT3lVz3nXHkZIhmSk3hOPRHDJKjdX0WUzk8rEQ1oq2mBjPl22BQUidsCVlnuhPokqWzreQ3/eGeH8G9iKkQXqY39vL5t88Yr8maJYMBNLfVAfbVblZmlruAqSMdakP3voWdkEQqT7nam3ZeNkYe8+vdBVwtsTsKAHpvydrouzjCfZr9pVYKjnDqqOe5ssgtxt/NtqDiczQutM/HDEUy/44AYJi8ljFSLfWgo4aVbz52XVAat12OELAEXggZsQMd1xXMtZCROtsshtGH+SvbVJ6ai2eHEDXqGpcp/mU6bEg8sgwbLipXzqMcLkfHWRO50UeSD7iint8Tc7pFvbr6QncY12YetLZ2XApzRDf2l9yxN+u2PbixPYHKDQrpL9+1GNC2MqBGiBLAY0PvljP+l/0+Wdh/uVlAUJXnBkifsKbLJLq6TmhLS3TKfdbRC84EkPbM4WGvWw1J5n9me67qzfr9EYv39cnKD3DwNMRRKg/efd1c0x3DzHWl1gB84= 21 | matrix: 22 | include: 23 | - os: linux 24 | env: 25 | - PLATFORM=gcc_64 26 | - os: linux 27 | env: 28 | - PLATFORM=android_armv7 29 | - os: linux 30 | env: 31 | - PLATFORM=android_x86 32 | - os: osx 33 | env: 34 | - PLATFORM=clang_64 35 | 36 | before_install: 37 | - git clone https://github.com/Skycoder42/QtModules.git ./qtmodules-travis 38 | - travis_wait 40 ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh 39 | 40 | script: 41 | - ./ci/travis_init.sh 42 | - ./qtmodules-travis/ci/$TRAVIS_OS_NAME/build.sh 43 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then ./ci/travis_postbuild.sh; fi 44 | 45 | before_deploy: 46 | - ./ci/travis_deploy.sh 47 | deploy: 48 | provider: releases 49 | skip_cleanup: true 50 | api_key: 51 | secure: mSzeSXiPdqGn7LoIWF7tPqM01IpA7Q44P8i3wB5V8jBqr5lRuhkLzE+j+EI6NzLtxZEYZLl/Y+s11E7Q8e36qQe0eJtlgb5CJC+CrAE4vGWV6BjZFhUsaB5fs8zLOhWL2fhdMiHUQaQrh1foWnvrtJqt2Av5jf9kTttrrqg/YGRly8omXe3MV+9ZbHcZTuDWISaD29nLPmr4obunrcjy8oWiP1MRJmPsEp3poTLTQjWSOBLlTKqK/JZtieTTS1vzG+lENxSxCaRGQRuEYji056KudKEvx8lxI1cP6nPG+h7L74plYIGRZZ3V4Ux7ytnn6UfdBU45wRLT1bDSfG6nyGHT+4CetVFDH9+DE00PFz3oEcVOpRavb4eRArSq1r45coYtTdFKtjnN4o6kbZ77AdXKKJbUKks9wbV9jJmfyK1RP+snH5VmY5r2R5FlTg2zbcl4oYY3lSjzgCwbFkuEXT9NjWeKUX0CIZ5C7vUTh8Yk3hB6jFa3ZUxcTOT0pmzgC7RVmWG/1k55daoLClxpdF/HUr/x+OyXqFLMY7bXzYLLT1TxUZg3B5zcGbuZecrLs35f5Jr3cB+aJG+4CADDBoY+3Kw2jM8j433pYHuJa2P1Zl6BaZvsPkOvfaVD+9Zq+mcenwh2QQ8tcGAWgKcLd3TcaNhYVvxn7+mIY+5brFk= 52 | file_glob: true 53 | file: 54 | - ./install/repository_*.tar.xz 55 | - ./install/Ani-Rem Installer.* 56 | - ./install/*.apk 57 | on: 58 | repo: Skycoder42/AniRem 59 | tags: true 60 | 61 | before_cache: 62 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 63 | - rm -rf $HOME/.gradle/caches/*/plugin-resolution/ 64 | - rm -rf $QPMX_CACHE_DIR/locks 65 | - rm -rf $QPMX_CACHE_DIR/tmp 66 | cache: 67 | directories: 68 | - $HOME/.gradle/caches/ 69 | - $HOME/.gradle/wrapper/ 70 | - $HOME/.android/build-cache 71 | - $QPMX_CACHE_DIR 72 | -------------------------------------------------------------------------------- /icn/anirem_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 51 | 54 | 55 | 56 | 60 | Layer 1 62 | A 72 | nirem 82 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /icn/anirem.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 25 | 27 | 28 | 30 | image/svg+xml 31 | 33 | 34 | 35 | 36 | 37 | 39 | 60 | 63 | 64 | 65 | 68 | Layer 1 70 | A 80 | nirem 90 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /gui/core/loginviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "loginviewmodel.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | LoginViewModel::LoginViewModel(QObject *parent) : 8 | ViewModel(parent), 9 | _settings(nullptr), 10 | //WORKAROUND _user(ProxerApi::factory().user().instance(this)), 11 | _user((new ProxerApi(this))->user()), 12 | _userName(), 13 | _password(), 14 | _authCode(), 15 | _storePassword(false) 16 | { 17 | _user->setErrorTranslator(&ApiHelper::transformError); 18 | connect(_user, &UserClass::apiError, 19 | this, &LoginViewModel::apiError); 20 | } 21 | 22 | QString LoginViewModel::userName() const 23 | { 24 | return _userName; 25 | } 26 | 27 | QString LoginViewModel::password() const 28 | { 29 | return _password; 30 | } 31 | 32 | QString LoginViewModel::authCode() const 33 | { 34 | return _authCode; 35 | } 36 | 37 | bool LoginViewModel::storePassword() const 38 | { 39 | return _storePassword; 40 | } 41 | 42 | bool LoginViewModel::isLoading() const 43 | { 44 | return _loading; 45 | } 46 | 47 | void LoginViewModel::setUserName(const QString &userName) 48 | { 49 | if (_userName == userName) 50 | return; 51 | 52 | _userName = userName; 53 | emit userNameChanged(_userName); 54 | } 55 | 56 | void LoginViewModel::setPassword(const QString &password) 57 | { 58 | if (_password == password) 59 | return; 60 | 61 | _password = password; 62 | emit passwordChanged(_password); 63 | } 64 | 65 | void LoginViewModel::setAuthCode(const QString &authCode) 66 | { 67 | if (_authCode == authCode) 68 | return; 69 | 70 | _authCode = authCode; 71 | emit authCodeChanged(_authCode); 72 | } 73 | 74 | void LoginViewModel::setStorePassword(bool storePassword) 75 | { 76 | if (_storePassword == storePassword) 77 | return; 78 | 79 | _storePassword = storePassword; 80 | emit storePasswordChanged(_storePassword); 81 | } 82 | 83 | void LoginViewModel::login() 84 | { 85 | _loading = true; 86 | emit loadingChanged(_loading); 87 | 88 | QUrlQuery params; 89 | params.addQueryItem(QStringLiteral("username"), _userName); 90 | params.addQueryItem(QStringLiteral("password"), _password); 91 | if(!_authCode.isEmpty()) 92 | params.addQueryItem(QStringLiteral("secretkey"), _authCode); 93 | auto nmReply = _user->restClass()->builder() 94 | .addPath(QStringLiteral("login")) 95 | .setBody(params.query().toUtf8(), "application/x-www-form-urlencoded") 96 | .setVerb(QtRestClient::RestClass::PostVerb) 97 | .send(); 98 | 99 | auto reply = new QtRestClient::GenericRestReply { 100 | nmReply, 101 | _user->restClient(), 102 | this 103 | }; 104 | reply->onSucceeded([this](int code, ProxerLogin login){ 105 | if(ApiHelper::testValid(code, login) || 106 | code == 3012) { // user already logged in 107 | _settings->account.username = _userName; 108 | _settings->account.storedPw = _storePassword; 109 | AniRem::setProxerToken(login.data().token(), _settings); 110 | if(_storePassword) { 111 | //FEATURE store password to keystore 112 | } else { 113 | //FEATURE remove password from keystore 114 | } 115 | 116 | emit resultReady(true); 117 | emit loginSuccessful(); 118 | } else 119 | apiError(login.message(), login.code(), QtRestClient::RestReply::FailureError); 120 | }); 121 | reply->onAllErrors([this](QString e, int c, QtRestClient::RestReply::ErrorType t) { 122 | apiError(e, c, t); 123 | }); 124 | } 125 | 126 | void LoginViewModel::onInit(const QVariantHash ¶ms) 127 | { 128 | Q_UNUSED(params); 129 | setUserName(_settings->account.username); 130 | setStorePassword(_settings->account.storedPw); 131 | 132 | if(_storePassword) { 133 | //FEATURE load password from keystore 134 | } 135 | } 136 | 137 | void LoginViewModel::apiError(const QString &errorString, int errorCode, QtRestClient::RestReply::ErrorType errorType) 138 | { 139 | QtMvvm::critical(tr("Login failed"), 140 | ApiHelper::formatError(errorString, errorCode, errorType)); 141 | 142 | _loading = false; 143 | emit loadingChanged(_loading); 144 | } 145 | -------------------------------------------------------------------------------- /gui/widgets/addanimedialog.cpp: -------------------------------------------------------------------------------- 1 | #include "addanimedialog.h" 2 | #include "ui_addanimedialog.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | AddAnimeDialog::AddAnimeDialog(QtMvvm::ViewModel *viewModel, QWidget *parent) : 9 | QDialog(parent), 10 | _viewModel(static_cast(viewModel)), 11 | _ui(new Ui::AddAnimeDialog), 12 | _loadingMovie(new QMovie(QStringLiteral(":/animations/loading.gif"), "gif", this)), 13 | _currentPixmap(), 14 | _pmState(PmCleared) 15 | { 16 | _ui->setupUi(this); 17 | DialogMaster::masterDialog(this, true); 18 | _ui->proxerIDLineEdit->setValidator(new QIntValidator(0, INT_MAX, _ui->proxerIDLineEdit)); 19 | 20 | _ui->titleComboBox->setModel(_viewModel->nameModel()); 21 | 22 | connect(_viewModel->imageLoader(), &ImageLoader::imageLoaded, 23 | this, &AddAnimeDialog::imageLoaded, 24 | Qt::QueuedConnection); 25 | connect(_viewModel->imageLoader(), &ImageLoader::imageLoadFailed, 26 | this, &AddAnimeDialog::imageLoadFailed, 27 | Qt::QueuedConnection); 28 | 29 | connect(_ui->proxerIDLineEdit, &QLineEdit::editingFinished, 30 | this, &AddAnimeDialog::uiIdChanged); 31 | 32 | //viewmodel 33 | QtMvvm::bind(_viewModel, "id", 34 | this, "uiId"); 35 | QtMvvm::bind(_viewModel, "title", 36 | _ui->titleComboBox, "currentText"); 37 | QtMvvm::bind(_viewModel, "acceptable", 38 | _ui->buttonBox->button(QDialogButtonBox::Ok), "enabled", 39 | QtMvvm::Binding::OneWayToView); 40 | 41 | connect(_viewModel, &AddAnimeViewModel::loadingChanged, 42 | this, &AddAnimeDialog::loadingChanged); 43 | loadingChanged(_viewModel->isLoading()); 44 | 45 | connect(_viewModel, &AddAnimeViewModel::close, 46 | this, &AddAnimeDialog::reject); 47 | } 48 | 49 | AddAnimeDialog::~AddAnimeDialog() 50 | { 51 | delete _ui; 52 | } 53 | 54 | void AddAnimeDialog::accept() 55 | { 56 | if(_viewModel->accept()) 57 | QDialog::accept(); 58 | } 59 | 60 | int AddAnimeDialog::uiId() const 61 | { 62 | auto ok = false; 63 | auto res = QLocale().toInt(_ui->proxerIDLineEdit->text(), &ok); 64 | if(ok) 65 | return res; 66 | else 67 | return -1; 68 | } 69 | 70 | void AddAnimeDialog::setUiId(int id) 71 | { 72 | _pmState = PmCleared; 73 | if(id == -1) { 74 | _ui->proxerIDLineEdit->clear(); 75 | updatePm(); 76 | } else { 77 | _ui->proxerIDLineEdit->setText(QString::number(id)); 78 | _pmState = PmLoading; 79 | updatePm(); 80 | _viewModel->imageLoader()->loadImage(id); 81 | } 82 | } 83 | 84 | void AddAnimeDialog::loadingChanged(bool loading) 85 | { 86 | _ui->proxerIDLineEdit->setEnabled(!loading); 87 | updatePm(); 88 | } 89 | 90 | void AddAnimeDialog::imageLoaded(int id, const QImage &image) 91 | { 92 | if(_viewModel->id() == id) { 93 | _currentPixmap = QPixmap::fromImage(image); 94 | _pmState = PmLoaded; 95 | updatePm(); 96 | } 97 | } 98 | 99 | void AddAnimeDialog::imageLoadFailed(int id, const QString &error) 100 | { 101 | if(_viewModel->id() == id) { 102 | qWarning().noquote() << error; 103 | _pmState = PmError; 104 | updatePm(); 105 | } 106 | } 107 | 108 | void AddAnimeDialog::updatePm() 109 | { 110 | if(_viewModel->isLoading() || _pmState == PmLoading) { 111 | _loadingMovie->start(); 112 | _ui->previewLabel->clear(); 113 | _ui->previewLabel->setScaledContents(false); 114 | _ui->previewLabel->setMovie(_loadingMovie); 115 | } else { 116 | switch(_pmState) { 117 | case AddAnimeDialog::PmCleared: 118 | _currentPixmap = QPixmap(); 119 | _ui->previewLabel->clear(); 120 | _ui->previewLabel->setText(tr("Preview")); 121 | break; 122 | case AddAnimeDialog::PmLoaded: 123 | _ui->previewLabel->clear(); 124 | _ui->previewLabel->setScaledContents(true); 125 | _ui->previewLabel->setPixmap(_currentPixmap); 126 | break; 127 | case AddAnimeDialog::PmError: 128 | _currentPixmap = QPixmap(); 129 | _ui->previewLabel->clear(); 130 | _ui->previewLabel->setText(tr("No image")); 131 | break; 132 | default: 133 | Q_UNREACHABLE(); 134 | break; 135 | } 136 | _loadingMovie->stop(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /gui/widgets/detailsdockwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DetailsDockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 202 10 | 419 11 | 12 | 13 | 14 | 15 | 202 16 | 90 17 | 18 | 19 | 20 | Anime Details 21 | 22 | 23 | 24 | 25 | 202 26 | 0 27 | 28 | 29 | 30 | 31 | 0 32 | 33 | 34 | 0 35 | 36 | 37 | 0 38 | 39 | 40 | 0 41 | 42 | 43 | 0 44 | 45 | 46 | 47 | 48 | Qt::ScrollBarAlwaysOn 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 0 57 | 0 58 | 200 59 | 383 60 | 61 | 62 | 63 | 64 | 0 65 | 66 | 67 | 0 68 | 69 | 70 | 0 71 | 72 | 73 | 0 74 | 75 | 76 | 0 77 | 78 | 79 | 80 | 81 | 82 | 200 83 | 277 84 | 85 | 86 | 87 | 88 | 16777215 89 | 277 90 | 91 | 92 | 93 | Qt::RichText 94 | 95 | 96 | Qt::AlignCenter 97 | 98 | 99 | true 100 | 101 | 102 | 103 | 104 | 105 | 106 | Qt::RichText 107 | 108 | 109 | Qt::AlignHCenter|Qt::AlignTop 110 | 111 | 112 | true 113 | 114 | 115 | 6 116 | 117 | 118 | Qt::TextBrowserInteraction 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | --------------------------------------------------------------------------------