package pl.szczodrzynski.edziennik import android.app.Activity import android.app.ActivityManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.os.* import android.provider.Settings import android.util.Log import android.view.Gravity import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.ColorUtils import androidx.lifecycle.Observer import androidx.navigation.NavOptions import com.danimahardhika.cafebar.CafeBar import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mikepenz.iconics.IconicsColor import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsSize import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.ProfileDrawerItem import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem import com.mikepenz.materialdrawer.model.interfaces.IProfile import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.droidsonroids.gif.GifDrawable import pl.szczodrzynski.edziennik.App.APP_URL import pl.szczodrzynski.edziennik.data.api.events.* import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.* import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.network.ServerRequest import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2 import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.appManagerIntentList import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.drawer.NavDrawer import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem import pl.szczodrzynski.navlib.drawer.items.withAppTitle import java.io.File import java.io.IOException import java.util.* import kotlin.math.roundToInt class MainActivity : AppCompatActivity() { companion object { var useOldMessages = false const val TAG = "MainActivity" const val REQUEST_LOGIN_ACTIVITY = 20222 const val DRAWER_PROFILE_ADD_NEW = 200 const val DRAWER_PROFILE_SYNC_ALL = 201 const val DRAWER_PROFILE_EXPORT_DATA = 202 const val DRAWER_PROFILE_MANAGE = 203 const val DRAWER_ITEM_HOME = 1 const val DRAWER_ITEM_TIMETABLE = 11 const val DRAWER_ITEM_AGENDA = 12 const val DRAWER_ITEM_GRADES = 13 const val DRAWER_ITEM_MESSAGES = 17 const val DRAWER_ITEM_HOMEWORK = 14 const val DRAWER_ITEM_BEHAVIOUR = 15 const val DRAWER_ITEM_ATTENDANCE = 16 const val DRAWER_ITEM_ANNOUNCEMENTS = 18 const val DRAWER_ITEM_NOTIFICATIONS = 20 const val DRAWER_ITEM_SETTINGS = 101 const val DRAWER_ITEM_DEBUG = 102 const val TARGET_GRADES_EDITOR = 501 const val TARGET_HELP = 502 const val TARGET_FEEDBACK = 120 const val TARGET_MESSAGES_DETAILS = 503 const val TARGET_WEB_PUSH = 140 const val HOME_ID = DRAWER_ITEM_HOME val navTargetList: List by lazy { val list: MutableList = mutableListOf() // home item list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragmentV2::class) .withTitle(R.string.app_name) .withIcon(CommunityMaterial.Icon2.cmd_home_outline) .isInDrawer(true) .isStatic(true) .withPopToHome(false) list += NavTarget(DRAWER_ITEM_TIMETABLE, R.string.menu_timetable, TimetableFragment::class) .withIcon(CommunityMaterial.Icon2.cmd_timetable) .withBadgeTypeId(TYPE_LESSON_CHANGE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class) .withIcon(CommunityMaterial.Icon.cmd_calendar_outline) .withBadgeTypeId(TYPE_EVENT) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class) .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline) .withBadgeTypeId(TYPE_GRADE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class) .withIcon(CommunityMaterial.Icon.cmd_email_outline) .withBadgeTypeId(TYPE_MESSAGE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class) .withIcon(SzkolnyFont.Icon.szf_notebook_outline) .withBadgeTypeId(TYPE_HOMEWORK) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class) .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline) .withBadgeTypeId(TYPE_NOTICE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class) .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline) .withBadgeTypeId(TYPE_ATTENDANCE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class) .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline) .withBadgeTypeId(TYPE_ANNOUNCEMENT) .isInDrawer(true) // static drawer items list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class) .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class) .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) // profile settings items list += NavTarget(DRAWER_PROFILE_ADD_NEW, R.string.menu_add_new_profile, null) .withIcon(CommunityMaterial.Icon2.cmd_plus) .withDescription(R.string.drawer_add_new_profile_desc) .isInProfileList(true) list += NavTarget(DRAWER_PROFILE_MANAGE, R.string.menu_manage_profiles, ProfileManagerFragment::class) .withTitle(R.string.title_profile_manager) .withIcon(CommunityMaterial.Icon.cmd_account_group) .withDescription(R.string.drawer_manage_profiles_desc) .isInProfileList(false) list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null) .withIcon(CommunityMaterial.Icon.cmd_download_outline) .isInProfileList(true) // other target items, not directly navigated list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class) list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class) list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class) list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class) list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class) list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) list } } val b: ActivitySzkolnyBinding by lazy { ActivitySzkolnyBinding.inflate(layoutInflater) } val navView: NavView by lazy { b.navView } val drawer: NavDrawer by lazy { navView.drawer } val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } val app: App by lazy { applicationContext as App } private val fragmentManager by lazy { supportFragmentManager } private lateinit var navTarget: NavTarget val navTargetId get() = navTarget.id private val navBackStack = mutableListOf() private var navLoading = true /* ____ _____ _ / __ \ / ____| | | | | | |_ __ | | _ __ ___ __ _| |_ ___ | | | | '_ \ | | | '__/ _ \/ _` | __/ _ \ | |__| | | | | | |____| | | __/ (_| | || __/ \____/|_| |_| \_____|_| \___|\__,_|\__\__*/ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(Themes.appTheme) app.config.ui.language?.let { setLanguage(it) } setContentView(b.root) Log.d(TAG, Signing.appPassword) errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) navLoading = true b.navView.apply { drawer.init(this@MainActivity) SystemBarsUtil(this@MainActivity).run { //paddingByKeyboard = b.navView appFullscreen = false statusBarColor = getColorFromAttr(context, android.R.attr.colorBackground) statusBarDarker = false statusBarFallbackLight = COLOR_HALF_TRANSPARENT statusBarFallbackGradient = COLOR_HALF_TRANSPARENT navigationBarTransparent = false b.navView.configSystemBarsUtil(this) // fix for setting status bar color to window color, outside of navlib if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { window.statusBarColor = statusBarColor } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ColorUtils.calculateLuminance(statusBarColor) > 0.6) { window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR } // TODO fix navlib navbar detection, orientation change issues, status bar color setting if not fullscreen commit() } toolbar.apply { subtitleFormat = R.string.toolbar_subtitle subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread } bottomBar.apply { fabEnable = false fabExtendable = true fabExtended = false fabGravity = Gravity.CENTER if (Themes.isDark) { setBackgroundColor(blendColors( getColorFromAttr(context, R.attr.colorSurface), getColorFromRes(R.color.colorSurface_4dp) )) elevation = dpToPx(4).toFloat() } } bottomSheet.apply { removeAllItems() toggleGroupEnabled = false textInputEnabled = false } drawer.apply { setAccountHeaderBackground(app.config.ui.headerBackground) drawerProfileListEmptyListener = { app.config.loginFinished = false app.saveConfig("loginFinished") profileListEmptyListener() } drawerItemSelectedListener = { id, position, drawerItem -> loadTarget(id) true } drawerProfileSelectedListener = { id, profile, _, _ -> loadProfile(id) false } drawerProfileLongClickListener = { _, profile, _, view -> if (profile is ProfileDrawerItem) { showProfileContextMenu(profile, view) true } else { false } } drawerProfileImageLongClickListener = drawerProfileLongClickListener drawerProfileSettingClickListener = this@MainActivity.profileSettingClickListener miniDrawerVisibleLandscape = null miniDrawerVisiblePortrait = app.config.ui.miniMenuVisible } } navTarget = navTargetList[0] var profileListEmpty = drawer.profileListEmpty if (savedInstanceState != null) { intent?.putExtras(savedInstanceState) savedInstanceState.clear() } if (!profileListEmpty) { handleIntent(intent?.extras) } app.db.profileDao().allFull.observe(this, Observer { profiles -> // TODO fix weird -1 profiles ??? profiles.removeAll { it.id < 0 } drawer.setProfileList(profiles) if (profileListEmpty) { profileListEmpty = false handleIntent(intent?.extras) } else if (app.profile != null) { drawer.currentProfile = app.profile.id } }) // if null, getAllFull will load a profile and update drawerItems if (app.profile != null) setDrawerItems() app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters -> unreadCounters.map { it.type = it.thingType } drawer.setUnreadCounterList(unreadCounters) }) b.swipeRefreshLayout.isEnabled = true b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } b.swipeRefreshLayout.setColorSchemeResources( R.color.md_blue_500, R.color.md_amber_500, R.color.md_green_500 ) isStoragePermissionGranted() SyncWorker.scheduleNext(app) // APP BACKGROUND if (app.config.ui.appBackground != null) { try { app.config.ui.appBackground?.let { var bg = it val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg") if (bgDir.exists()) { val files = bgDir.listFiles() val r = Random() val i = r.nextInt(files.size) bg = files[i].toString() } val linearLayout = b.root if (bg.endsWith(".gif")) { linearLayout.background = GifDrawable(bg) } else { linearLayout.background = BitmapDrawable.createFromPath(bg) } } } catch (e: IOException) { e.printStackTrace() } } // IT'S WINTER MY DUDES val today = Date.getToday() if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) { b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) } // WHAT'S NEW DIALOG if (app.config.appVersion < BuildConfig.VERSION_CODE) { ServerRequest(app, app.requestScheme + APP_URL + "main.php?just_updated", "MainActivity/JU") .run { e, result -> Handler(Looper.getMainLooper()).post { try { ChangelogDialog().show(supportFragmentManager, "whats_new") } catch (e2: Exception) { e2.printStackTrace() } } } if (app.config.appVersion < 170) { //Intent intent = new Intent(this, ChangelogIntroActivity.class); //startActivity(intent); } else { app.config.appVersion = BuildConfig.VERSION_CODE } } // RATE SNACKBAR if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) { navView.coordinator.postDelayed({ CafeBar.builder(this) .content(R.string.rate_snackbar_text) .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) .positiveText(R.string.rate_snackbar_positive) .positiveColor(-0xb350b0) .negativeText(R.string.rate_snackbar_negative) .negativeColor(0xff666666.toInt()) .neutralText(R.string.rate_snackbar_neutral) .neutralColor(0xff666666.toInt()) .onPositive { cafeBar -> Utils.openGooglePlay(this) cafeBar.dismiss() app.config.appRateSnackbarTime = 0 } .onNegative { cafeBar -> Toast.makeText(this, "Szkoda, opinie innych pomagają mi rozwijać aplikację.", Toast.LENGTH_LONG).show() cafeBar.dismiss() app.config.appRateSnackbarTime = 0 } .onNeutral { cafeBar -> Toast.makeText(this, "OK", Toast.LENGTH_LONG).show() cafeBar.dismiss() app.config.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 } .autoDismiss(false) .swipeToDismiss(true) .floating(true) .show() }, 10000) } // CONTEXT MENU ITEMS bottomSheet.removeAllItems() bottomSheet.appendItems( BottomSheetPrimaryItem(false) .withTitle(R.string.menu_sync) .withIcon(CommunityMaterial.Icon.cmd_download_outline) .withOnClickListener(View.OnClickListener { bottomSheet.close() SyncViewListDialog(this, navTargetId) }), BottomSheetSeparatorItem(false), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_settings) .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_feedback) .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline) .withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) }) ) if (App.devMode) { bottomSheet += BottomSheetPrimaryItem(false) .withTitle(R.string.menu_debug) .withIcon(CommunityMaterial.Icon.cmd_android_studio) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) }) } EventBus.getDefault().register(this) } override fun onDestroy() { EventBus.getDefault().unregister(this) super.onDestroy() } var profileListEmptyListener = { startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY) } private var profileSettingClickListener = { id: Int, view: View? -> when (id) { DRAWER_PROFILE_ADD_NEW -> { LoginActivity.privacyPolicyAccepted = true // else it would try to navigateUp onBackPressed, which it can't do. There is no parent fragment LoginActivity.firstCompleted = false profileListEmptyListener() } DRAWER_PROFILE_SYNC_ALL -> { EdziennikTask.sync().enqueue(this) } else -> { loadTarget(id) } } false } /* _____ / ____| | (___ _ _ _ __ ___ \___ \| | | | '_ \ / __| ____) | |_| | | | | (__ |_____/ \__, |_| |_|\___| __/ | |__*/ fun syncCurrentFeature() { swipeRefreshLayout.isRefreshing = true Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show() val fragmentParam = when (navTargetId) { DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection else -> 0 } val arguments = when (navTargetId) { DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d) else -> null } EdziennikTask.syncProfile( App.profileId, listOf(navTargetId to fragmentParam), arguments ).enqueue(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncStartedEvent(event: ApiTaskStartedEvent) { swipeRefreshLayout.isRefreshing = true if (event.profileId == App.profileId) { navView.toolbar.apply { subtitleFormat = null subtitleFormatWithUnread = null subtitle = getString(R.string.toolbar_subtitle_syncing) } } } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncProgressEvent(event: ApiTaskProgressEvent) { if (event.profileId == App.profileId) { navView.toolbar.apply { subtitleFormat = null subtitleFormatWithUnread = null subtitle = if (event.progress < 0f) event.progressText ?: "" else getString(R.string.toolbar_subtitle_syncing_format, event.progress.roundToInt(), event.progressText ?: "") } } } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncProfileFinishedEvent(event: ApiTaskFinishedEvent) { if (event.profileId == App.profileId) { navView.toolbar.apply { subtitleFormat = R.string.toolbar_subtitle subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread subtitle = "Gotowe" } } } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) { swipeRefreshLayout.isRefreshing = false } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncErrorEvent(event: ApiTaskErrorEvent) { navView.toolbar.apply { subtitleFormat = R.string.toolbar_subtitle subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread subtitle = "Gotowe" } errorSnackbar.addError(event.error).show() } @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { if (app.appConfig.dontShowAppManagerDialog) return MaterialAlertDialogBuilder(this) .setTitle(R.string.app_manager_dialog_title) .setMessage(R.string.app_manager_dialog_text) .setPositiveButton(R.string.ok) { dialog, which -> try { for (intent in appManagerIntentList) { if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { startActivity(intent) } } } catch (e: Exception) { try { startActivity(Intent(Settings.ACTION_SETTINGS)) } catch (e: Exception) { e.printStackTrace() Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT).show() } } } .setNeutralButton(R.string.dont_ask_again) { dialog, which -> app.appConfig.dontShowAppManagerDialog = true app.saveConfig("dontShowAppManagerDialog") } .setCancelable(false) .show() } private fun fragmentToSyncName(currentFragment: Int): Int { return when (currentFragment) { DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable DRAWER_ITEM_AGENDA -> R.string.sync_feature_agenda DRAWER_ITEM_GRADES -> R.string.sync_feature_grades DRAWER_ITEM_HOMEWORK -> R.string.sync_feature_homework DRAWER_ITEM_BEHAVIOUR -> R.string.sync_feature_notices DRAWER_ITEM_ATTENDANCE -> R.string.sync_feature_attendance DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) { 1 -> R.string.sync_feature_messages_outbox else -> R.string.sync_feature_messages_inbox } DRAWER_ITEM_ANNOUNCEMENTS -> R.string.sync_feature_announcements else -> R.string.sync_feature_syncing_all } } /* _____ _ _ |_ _| | | | | | | _ __ | |_ ___ _ __ | |_ ___ | | | '_ \| __/ _ \ '_ \| __/ __| _| |_| | | | || __/ | | | |_\__ \ |_____|_| |_|\__\___|_| |_|\__|__*/ private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { handleIntent(intent?.extras) } } private fun handleIntent(extras: Bundle?) { d(TAG, "handleIntent() {") extras?.keySet()?.forEach { key -> d(TAG, " \"$key\": "+extras.get(key)) } d(TAG, "}") if (extras?.containsKey("reloadProfileId") == true) { val reloadProfileId = extras.getInt("reloadProfileId", -1) extras.remove("reloadProfileId") if (reloadProfileId == -1 || (app.profile != null && app.profile.id == reloadProfileId)) { reloadTarget() return } } var intentProfileId = -1 var intentTargetId = -1 if (extras?.containsKey("profileId") == true) { intentProfileId = extras.getInt("profileId", -1) extras.remove("profileId") } if (extras?.containsKey("fragmentId") == true) { intentTargetId = extras.getInt("fragmentId", -1) extras.remove("fragmentId") } /*if (intentTargetId == -1 && navController.currentDestination?.id == R.id.loadingFragment) { intentTargetId = navTarget.id }*/ if (navLoading) { b.fragment.removeAllViews() if (intentTargetId == -1) intentTargetId = HOME_ID } when { app.profile == null || app.profile.id == -1 -> { if (intentProfileId == -1) intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1) loadProfile(intentProfileId, intentTargetId, extras) } intentProfileId != -1 -> { if (app.profile.id != intentProfileId) loadProfile(intentProfileId, intentTargetId, extras) else loadTarget(intentTargetId, extras) } intentTargetId != -1 -> { drawer.currentProfile = app.profile.id if (navTargetId != intentTargetId || navLoading) loadTarget(intentTargetId, extras) } else -> { drawer.currentProfile = app.profile.id } } navLoading = false } override fun recreate() { recreate(navTargetId) } fun recreate(targetId: Int) { recreate(targetId, null) } fun recreate(targetId: Int? = null, arguments: Bundle? = null) { val intent = Intent(this, MainActivity::class.java) if (arguments != null) intent.putExtras(arguments) if (targetId != null) { intent.putExtra("fragmentId", targetId) } finish() overridePendingTransition(R.anim.fade_in, R.anim.fade_out) startActivity(intent) } override fun onResume() { val filter = IntentFilter() filter.addAction(Intent.ACTION_MAIN) registerReceiver(intentReceiver, filter) super.onResume() } override fun onPause() { unregisterReceiver(intentReceiver) super.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt("fragmentId", navTargetId) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) handleIntent(intent?.extras) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_LOGIN_ACTIVITY) { if (resultCode == Activity.RESULT_CANCELED && false) { finish() } else { if (!app.config.loginFinished) finish() else { handleIntent(data?.extras) } } } } /* _ _ _ _ _ | | | | | | | | | | | | ___ __ _ __| | _ __ ___ ___| |_| |__ ___ __| |___ | | / _ \ / _` |/ _` | | '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __| | |___| (_) | (_| | (_| | | | | | | | __/ |_| | | | (_) | (_| \__ \ |______\___/ \__,_|\__,_| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|__*/ val navOptions = NavOptions.Builder() .setEnterAnim(R.anim.task_open_enter) // new fragment enter .setExitAnim(R.anim.task_open_exit) // old fragment exit .setPopEnterAnim(R.anim.task_close_enter) // old fragment enter back .setPopExitAnim(R.anim.task_close_exit) // new fragment exit .build() fun loadProfile(id: Int) = loadProfile(id, navTargetId) fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments) fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { //d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)") if (app.profile != null && App.profileId == id) { drawer.currentProfile = app.profile.id loadTarget(drawerSelection, arguments) return } AsyncTask.execute { app.profileLoadById(id) this.runOnUiThread { if (app.profile == null) { LoginActivity.firstCompleted = false if (app.config.loginFinished) { // this shouldn't run profileListEmptyListener() } } else { setDrawerItems() // the drawer profile is updated automatically when the drawer item is clicked // update it manually when switching profiles from other source //if (drawer.currentProfile != app.profile.id) drawer.currentProfile = app.profile.id loadTarget(drawerSelection, arguments) } } } } fun loadTarget(id: Int, arguments: Bundle? = null) { var loadId = id if (loadId == -1) { loadId = DRAWER_ITEM_HOME } val target = navTargetList .singleOrNull { it.id == loadId } if (target == null) { Toast.makeText(this, getString(R.string.error_invalid_fragment, id), Toast.LENGTH_LONG).show() loadTarget(navTargetList.first(), arguments) } else { loadTarget(target, arguments) } } private fun loadTarget(target: NavTarget, arguments: Bundle? = null) { d("NavDebug", "loadItem(id = ${target.id})") bottomSheet.close() bottomSheet.removeAllContextual() bottomSheet.toggleGroupEnabled = false bottomSheet.onCloseListener = null drawer.close() drawer.setSelection(target.id, fireOnClick = false) navView.toolbar.setTitle(target.title ?: target.name) navView.bottomBar.fabEnable = false navView.bottomBar.fabExtended = false navView.bottomBar.setFabOnClickListener(null) d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}") val fragment = target.fragmentClass?.java?.newInstance() ?: return fragment.arguments = arguments val transaction = fragmentManager.beginTransaction() if (navTarget == target) { // just reload the current target transaction.setCustomAnimations( R.anim.fade_in, R.anim.fade_out ) } else { navBackStack.lastIndexOf(target).let { if (it == -1) return@let target // pop the back stack up until that target transaction.setCustomAnimations( R.anim.task_close_enter, R.anim.task_close_exit ) // navigating grades_add -> grades // navTarget == grades_add // navBackStack = [home, grades, grades_editor] // it == 1 // // navTarget = target // remove 1 // remove 2 val popCount = navBackStack.size - it for (i in 0 until popCount) { navBackStack.removeAt(navBackStack.lastIndex) } navTarget = target return@let null }?.let { // target is neither current nor in the back stack // so navigate to it transaction.setCustomAnimations( R.anim.task_open_enter, R.anim.task_open_exit ) navBackStack.add(navTarget) navTarget = target } } if (navTarget.popToHome) { // if the current has popToHome, let only home be in the back stack // probably `if (navTarget.popToHome)` in popBackStack() is not needed now val popCount = navBackStack.size - 1 for (i in 0 until popCount) { navBackStack.removeAt(navBackStack.lastIndex) } } d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:") navBackStack.forEachIndexed { index, target2 -> d("NavDebug", " - $index: ${target2.fragmentClass?.java?.simpleName}") } transaction.replace(R.id.fragment, fragment) transaction.commitAllowingStateLoss() // TASK DESCRIPTION if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) val taskDesc = ActivityManager.TaskDescription( if (target.id == HOME_ID) getString(R.string.app_name) else getString(R.string.app_task_format, getString(target.name)), bm, getColorFromAttr(this, R.attr.colorSurface) ) setTaskDescription(taskDesc) } } fun reloadTarget() = loadTarget(navTarget) private fun popBackStack(): Boolean { if (navBackStack.size == 0) { return false } // TODO back stack argument support if (navTarget.popToHome) { loadTarget(HOME_ID) } else { loadTarget(navBackStack.last()) } return true } fun navigateUp() { if (!popBackStack()) { super.onBackPressed() } } /** * Use the NavLib's menu button ripple to gain user attention * that something has changed in the bottom sheet. */ fun gainAttention() { b.navView.postDelayed({ navView.gainAttentionOnBottomBar() }, 2000) } fun gainAttentionFAB() { navView.bottomBar.fabExtended = false b.navView.postDelayed({ navView.bottomBar.fabExtended = true }, 1000) b.navView.postDelayed({ navView.bottomBar.fabExtended = false }, 3000) } /* _____ _ _ | __ \ (_) | | | | |_ __ __ ___ _____ _ __ _| |_ ___ _ __ ___ ___ | | | | '__/ _` \ \ /\ / / _ \ '__| | | __/ _ \ '_ ` _ \/ __| | |__| | | | (_| |\ V V / __/ | | | || __/ | | | | \__ \ |_____/|_| \__,_| \_/\_/ \___|_| |_|\__\___|_| |_| |_|__*/ private fun createDrawerItem(target: NavTarget, level: Int = 1): IDrawerItem<*> { val item = DrawerPrimaryItem() .withIdentifier(target.id.toLong()) .withName(target.name) .withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id)) .also { if (target.description != null) it.withDescription(target.description!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) } .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) } .also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)} if (target.badgeTypeId != null) drawer.addUnreadCounterType(target.badgeTypeId!!, target.id) // TODO sub items /* if (target.subItems != null) { for (subItem in target.subItems!!) { item.subItems += createDrawerItem(subItem, level+1) } }*/ return item } fun setDrawerItems() { d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}") val drawerItems = arrayListOf>() val drawerProfiles = arrayListOf() val supportedFragments = if (app.profile == null) arrayListOf() else app.profile.supportedFragments targetPopToHomeList.clear() var separatorAdded = false for (target in navTargetList) { if (target.isInDrawer && target.isBelowSeparator && !separatorAdded) { separatorAdded = true drawerItems += DividerDrawerItem() } if (target.popToHome) targetPopToHomeList += target.id if (target.isInDrawer && (target.isStatic || supportedFragments.isEmpty() || supportedFragments.contains(target.id))) { drawerItems += createDrawerItem(target) if (target.id == 1) { targetHomeId = target.id } } if (target.isInProfileList) { drawerProfiles += ProfileSettingDrawerItem() .withIdentifier(target.id.toLong()) .withName(target.name) .also { if (target.description != null) it.withDescription(target.description!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) } } } // seems that this cannot be open, because the itemAdapter has Profile items // instead of normal Drawer items... drawer.profileSelectionClose() drawer.setItems(*drawerItems.toTypedArray()) drawer.removeAllProfileSettings() drawer.addProfileSettings(*drawerProfiles.toTypedArray()) } private fun showProfileContextMenu(profile: IProfile<*>, view: View) { val profileId = profile.identifier.toInt() val popupMenu = PopupMenu(this, view) popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings) popupMenu.menu.add(0, 2, 2, R.string.profile_menu_remove) popupMenu.setOnMenuItemClickListener { item -> if (item.itemId == 1) { if (profileId != app.profile.id) { loadProfile(profileId, DRAWER_ITEM_SETTINGS) return@setOnMenuItemClickListener true } loadTarget(DRAWER_ITEM_SETTINGS, null) } else if (item.itemId == 2) { ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?") } true } popupMenu.show() } private val targetPopToHomeList = arrayListOf() private var targetHomeId: Int = -1 override fun onBackPressed() { if (!b.navView.onBackPressed()) { if (App.getConfig().ui.openDrawerOnBackPressed) { b.navView.drawer.toggle() } else { navigateUp() } } } }