From d8e52335cb3ed757fbc8e22e905c3c2f65a284dd Mon Sep 17 00:00:00 2001 From: Odweta Date: Fri, 9 Feb 2024 18:50:50 +0100 Subject: [PATCH] phase 2 i forgor --- .../java/com/odweta/solen/AboutFragment.kt | 61 ++++ .../java/com/odweta/solen/CalWeekFragment.kt | 191 ++++++++++- .../java/com/odweta/solen/MainActivity.kt | 311 +++++++++++------- .../java/com/odweta/solen/MainFragment.kt | 22 +- .../java/com/odweta/solen/MenuFragment.kt | 11 +- .../java/com/odweta/solen/SettingsFragment.kt | 58 ++++ .../java/com/odweta/solen/SignInFragment.kt | 78 ++++- .../com/odweta/solen/SignInMainFragment.kt | 21 +- .../java/com/odweta/solen/SplashFragment.kt | 30 +- .../src/main/java/com/odweta/solen/Vars.kt | 2 + mobile/app/src/main/res/anim/fade_in.xml | 8 +- mobile/app/src/main/res/anim/fade_out.xml | 8 +- mobile/app/src/main/res/anim/slide_down.xml | 7 +- .../src/main/res/anim/slide_in_from_right.xml | 7 +- .../src/main/res/anim/slide_out_to_right.xml | 7 +- mobile/app/src/main/res/anim/slide_up.xml | 8 +- .../res/drawable/alert_dialog_background.xml | 4 +- .../main/res/drawable/button_background.xml | 4 +- .../{drawable-res => drawable}/loading2.gif | Bin mobile/app/src/main/res/drawable/loading3.gif | Bin 0 -> 131296 bytes .../res/{drawable-res => drawable}/solen4.png | Bin .../solen_yellow_logo.png | Bin .../app/src/main/res/drawable/solon_500.png | Bin 0 -> 15671 bytes .../main/res/drawable/solon_500_yellow.png | Bin 0 -> 15040 bytes .../squiggly_line_100_yellow.png | Bin .../squiggly_line_100_yellow_transparent.png | Bin ..._100_yellow_transparent_one_color_edge.png | Bin 0 -> 3355 bytes ...ggly_line_yellow_transparent_one_color.png | Bin .../main/res/drawable/toolbar_background.xml | 4 +- .../src/main/res/layout/activity_about.xml | 128 ++++--- .../main/res/layout/activity_container.xml | 5 +- .../app/src/main/res/layout/activity_main.xml | 14 +- .../src/main/res/layout/activity_settings.xml | 83 ++--- .../src/main/res/layout/activity_sign_in.xml | 11 +- .../res/layout/activity_splash_screen.xml | 20 +- .../main/res/layout/alert_dialog_sign_out.xml | 19 +- .../main/res/layout/radio_group_language.xml | 16 +- mobile/app/src/main/res/loading2.gif | Bin 0 -> 127794 bytes mobile/app/src/main/res/solen3.png | Bin 0 -> 10234 bytes mobile/app/src/main/res/solen4.png | Bin 0 -> 7096 bytes mobile/app/src/main/res/squiggly_line_100.png | Bin 0 -> 1283 bytes .../squiggly_line_100_yellow_transparent.png | Bin 0 -> 1966 bytes ...ggly_line_yellow_transparent_one_color.png | Bin 0 -> 1728 bytes mobile/app/src/main/res/values/colors.xml | 9 +- mobile/app/src/main/res/values/strings.xml | 1 + mobile/app/src/main/res/values/themes.xml | 17 + 46 files changed, 850 insertions(+), 315 deletions(-) rename mobile/app/src/main/res/{drawable-res => drawable}/loading2.gif (100%) create mode 100644 mobile/app/src/main/res/drawable/loading3.gif rename mobile/app/src/main/res/{drawable-res => drawable}/solen4.png (100%) rename mobile/app/src/main/res/{drawable-res => drawable}/solen_yellow_logo.png (100%) create mode 100644 mobile/app/src/main/res/drawable/solon_500.png create mode 100644 mobile/app/src/main/res/drawable/solon_500_yellow.png rename mobile/app/src/main/res/{drawable-res => drawable}/squiggly_line_100_yellow.png (100%) rename mobile/app/src/main/res/{drawable-res => drawable}/squiggly_line_100_yellow_transparent.png (100%) create mode 100644 mobile/app/src/main/res/drawable/squiggly_line_100_yellow_transparent_one_color_edge.png rename mobile/app/src/main/res/{drawable-res => drawable}/squiggly_line_yellow_transparent_one_color.png (100%) create mode 100644 mobile/app/src/main/res/loading2.gif create mode 100644 mobile/app/src/main/res/solen3.png create mode 100644 mobile/app/src/main/res/solen4.png create mode 100644 mobile/app/src/main/res/squiggly_line_100.png create mode 100644 mobile/app/src/main/res/squiggly_line_100_yellow_transparent.png create mode 100644 mobile/app/src/main/res/squiggly_line_yellow_transparent_one_color.png diff --git a/mobile/app/src/main/java/com/odweta/solen/AboutFragment.kt b/mobile/app/src/main/java/com/odweta/solen/AboutFragment.kt index e3e8e3d..48cb959 100644 --- a/mobile/app/src/main/java/com/odweta/solen/AboutFragment.kt +++ b/mobile/app/src/main/java/com/odweta/solen/AboutFragment.kt @@ -10,7 +10,9 @@ import android.text.SpannableString import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.UnderlineSpan +import android.view.GestureDetector import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.Window @@ -18,9 +20,15 @@ import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment +import kotlin.math.abs class AboutFragment : DialogFragment() { + private lateinit var gestureDetector: GestureDetector + private var initialTouchY: Float = 0f + private val SWIPETHRESHOLD = 100 + private val SWIPEVELOCITYTHRESHOLD = 100 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.Theme_Solen) @@ -32,6 +40,14 @@ class AboutFragment : DialogFragment() { ): View? { val view = inflater.inflate(R.layout.activity_about, container, false) + // Set up GestureDetector + gestureDetector = GestureDetector(requireContext(), SwipeGestureListener()) + + // Attach the gesture detector to the root layout + view.setOnTouchListener { _, event -> + handleTouchEvent(event) + } + val link = view.findViewById(R.id.link) val underlinedText = SpannableString(link.text) underlinedText.setSpan(UnderlineSpan(), 1, link.text.length, 0) @@ -52,6 +68,7 @@ class AboutFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) + dialog.window?.attributes?.windowAnimations = R.style.Theme_Solen_DialogAnimation dialog.window?.requestFeature(Window.FEATURE_NO_TITLE) dialog.setCancelable(true) @@ -80,4 +97,48 @@ class AboutFragment : DialogFragment() { link.text = spannable link.movementMethod = LinkMovementMethod.getInstance() } + + inner class SwipeGestureListener : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent): Boolean { + return true + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + val diffY = e1?.let { e2.y.minus(it.y) } ?: 0f + if (diffY > SWIPETHRESHOLD && abs(velocityY) > SWIPEVELOCITYTHRESHOLD) { + // Swiped down + dismiss() + return true + } + return false + } + } + + private fun handleTouchEvent(event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + initialTouchY = event.rawY + } + MotionEvent.ACTION_MOVE -> { + val deltaY = event.rawY - initialTouchY + dialog?.window?.attributes?.y = deltaY.toInt() + dialog?.window?.attributes?.windowAnimations = 0 + dialog?.show() + + // Check if the dialog has been dragged beyond a threshold and dismiss if necessary + if (deltaY > 200) { + dismiss() + } + } + MotionEvent.ACTION_UP -> { + initialTouchY = 0f + } + } + return true + } } \ No newline at end of file diff --git a/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt b/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt index 3b712bb..f13d4af 100644 --- a/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt +++ b/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt @@ -5,6 +5,9 @@ import android.content.res.Resources import android.graphics.Typeface import android.graphics.drawable.GradientDrawable import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -21,8 +24,17 @@ import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.LocalTime class CalWeekFragment : Fragment() { override fun onCreateView( @@ -33,10 +45,11 @@ class CalWeekFragment : Fragment() { val view = inflater.inflate(R.layout.activity_cal_week, container, false) val viewPager: ViewPager2 = view.findViewById(R.id.dayPager) + val adapter = DayPagerAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) for (i in 0 until Vars.days.size) { - adapter.addFragment(DayFragment(i)) + adapter.addFragment(DayFragment(i, view)) } viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { @@ -47,8 +60,11 @@ class CalWeekFragment : Fragment() { }) viewPager.adapter = adapter + viewPager.offscreenPageLimit = adapter.itemCount viewPager.currentItem = Vars.selectedDay + view.tag = "timetable_root" + return view } } @@ -71,7 +87,9 @@ class DayPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : } } -class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { +class DayFragment(private val position: Int, private val rootView: View) : Fragment(R.layout.fragment_day) { + private val handler = Handler(Looper.getMainLooper()) + @SuppressLint("ClickableViewAccessibility") override fun onCreateView( inflater: LayoutInflater, @@ -79,25 +97,165 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_day, container, false) + view.tag = "day_${position}_root" val dayLayout: LinearLayout = view.findViewById(R.id.layoutDayMain) + dayLayout.tag = "day_${position}_layout" Vars.dayMap[position]?.let { createDayView(it, dayLayout, resources) } return view } + + private fun highlightCurrent( + subj: Subject, + day: Day, + subjectDrawable: GradientDrawable, + subjectStroke: Int, + i: Int + ) { + handler.postDelayed(object : Runnable { + override fun run() { + subjectDrawable.setStroke(0, 0) + + var now = LocalTime.now() + var nowD = LocalDate.now().dayOfMonth + var nowH = now.hour + var nowM = now.minute + var nowT = LocalTime.of(nowH, nowM) + + lateinit var subjST: LocalTime + lateinit var subjET: LocalTime + val subjD = day.date.split(".")[0].toInt() + + if ("T" !in subj.start && "T" !in subj.end) { + val subjSH = subj.start.split(":")[0].toInt() + val subjSM = subj.start.split(":")[1].toInt() + + val subjEH = subj.end.split(":")[0].toInt() + val subjEM = subj.end.split(":")[1].toInt() + + subjST = LocalTime.of(subjSH, subjSM) + subjET = LocalTime.of(subjEH, subjEM) + } else { + val subjSH = subj.start + .split("T")[1] + .split(":")[0] + .toInt() + val subjSM = subj.start + .split("T")[1] + .split(":")[1] + .toInt() + + val subjEH = subj.end + .split("T")[1] + .split(":")[0] + .toInt() + val subjEM = subj.end + .split("T")[1] + .split(":")[1] + .toInt() + + subjST = LocalTime.of(subjSH, subjSM) + subjET = LocalTime.of(subjEH, subjEM) + } + + if ( + nowT in subjST..subjET && + nowD == subjD + ) { + if (i < day.subjects.size - 1 && !day.subjects[i + 1].free && !subj.free) { + subjectDrawable.setStroke( + 10, + ContextCompat.getColor( + requireContext(), + subjectStroke + ) + ) + } else if (i < day.subjects.size - 1 && day.subjects[i + 1].free && nowT != subjET) { + subjectDrawable.setStroke( + 10, + ContextCompat.getColor( + requireContext(), + subjectStroke + ) + ) + } else if (i < day.subjects.size - 1 && subj.free && !day.subjects[i + 1].free && nowT != subjET) { + subjectDrawable.setStroke( + 10, + ContextCompat.getColor( + requireContext(), + subjectStroke + ) + ) + } + } else if ( + nowD == subjD && + i > 0 + ) { + lateinit var prevET: LocalTime + + val prev = day.subjects[i - 1] + if ("T" !in prev.start && "T" !in prev.end) { + val prevEH = prev.end + .split(":")[0] + .toInt() + val prevEM = prev.end + .split(":")[1] + .toInt() + + prevET = LocalTime.of(prevEH, prevEM) + } else { + val prevEH = prev.end + .split("T")[1] + .split(":")[0] + .toInt() + val prevEM = prev.end + .split("T")[1] + .split(":")[1] + .toInt() + + prevET = LocalTime.of(prevEH, prevEM) + } + + if ( + nowT < subjST && + nowT > prevET + ) { + subjectDrawable.setStroke( + 10, + ContextCompat.getColor( + requireContext(), + subjectStroke + ) + ) + } + } + + rootView.postInvalidate() + handler.postDelayed(this, 40000L) + } + }, 0L) + } + + override fun onDestroyView() { + super.onDestroyView() + // Stop updating UI when the view is destroyed + handler.removeCallbacksAndMessages(null) + } + private fun createDayView(day: Day, rootLayout: LinearLayout, resources: Resources) { val customFont = context?.let { ResourcesCompat.getFont(it, R.font.roboto) } val scrollView = ScrollView(requireContext()) + scrollView.scrollBarStyle = R.style.Theme_Solen_Scrollbar val dayLayout = LinearLayout(requireContext()) dayLayout.orientation = LinearLayout.VERTICAL val nameDateTextView = TextView(requireContext()) - val nameDateString = day.name + " " + day.date - nameDateTextView.typeface = customFont - nameDateTextView.text = nameDateString - nameDateTextView.textSize = 30f + nameDateTextView.text = "${day.name} ${day.date}" + nameDateTextView.setTypeface(customFont, Typeface.BOLD_ITALIC) + nameDateTextView.textSize = 27f nameDateTextView.setTextColor( ContextCompat.getColor( requireContext(), @@ -170,18 +328,17 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { subjectBackground ) ) - subjectDrawable.setStroke( - 10, - ContextCompat.getColor( - requireContext(), - subjectStroke - ) - ) + + // launch a periodic check for highlighting the current lecture + highlightCurrent(subj, day, subjectDrawable, subjectStroke, i) + val subjectLayout = LinearLayout(requireContext()) subjectLayout.orientation = LinearLayout.HORIZONTAL subjectLayout.gravity = Gravity.CENTER_VERTICAL subjectLayout.background = subjectDrawable + // set the tag for later use (used as an id) + subjectLayout.tag = subj.number val subjectNumberDrawable = GradientDrawable() subjectNumberDrawable.shape = GradientDrawable.RECTANGLE @@ -192,13 +349,13 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { subjectBackground ) ) - subjectNumberDrawable.setStroke( + /*subjectNumberDrawable.setStroke( 10, ContextCompat.getColor( requireContext(), subjectStroke ) - ) + )*/ val subjectNumberTextView = TextView(requireContext()) subjectNumberTextView.typeface = customFont @@ -309,7 +466,7 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { val subjectTeacherTextView = TextView(requireContext()) subjectTeacherTextView.text = subj.teacher subjectTeacherTextView.typeface = customFont - subjectTeacherTextView.textSize = 15f + subjectTeacherTextView.textSize = 12f subjectTeacherTextView.setPadding(10, 0, 20, 10) subjectTeacherTextView.textAlignment = View.TEXT_ALIGNMENT_TEXT_END subjectTeacherTextView.setTextColor( @@ -395,7 +552,7 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) { dayLayout.addView(subjectSeparator) } else if (i != 0) { val subjectSeparator = ImageView(requireContext()) - subjectSeparator.setImageResource(R.drawable.squiggly_line_100x100) + subjectSeparator.setImageResource(R.drawable.squiggly_line_100_yellow_transparent_one_color_edge) subjectSeparator.layoutParams = LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, Util.pixelsToDp(20, resources) diff --git a/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt b/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt index c2c501c..6ed6cde 100644 --- a/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt +++ b/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt @@ -1,18 +1,24 @@ package com.odweta.solen +import android.app.Dialog import android.content.Context import android.content.Intent import android.os.Bundle -import android.util.Log +import android.view.View +import android.view.ViewGroup import android.widget.Button import android.widget.EditText +import android.widget.FrameLayout import android.widget.ImageView +import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast +import androidx.transition.AutoTransition import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope -import com.bumptech.glide.Glide import com.google.android.material.bottomnavigation.BottomNavigationView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -20,19 +26,39 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONObject -class MainActivity : AppCompatActivity() { +interface MainActivityListener { + fun setupMain(view: View, savedInstanceState: Bundle?) + fun onSignIn(view: View, wantDelete: Boolean, dialog: Dialog?) +} + +class MainActivity : AppCompatActivity(), MainActivityListener { private var week: List = mutableListOf() private var toastShowing: Boolean = false private var toastShown: Boolean = false private var toast2Shown: Boolean = false private var done: Boolean = false - private lateinit var toolbar: TextView private var role: String = " - Student" private lateinit var navbar: BottomNavigationView - + private lateinit var toolbar: TextView // @section COMPANION companion object { + private var intfc: MainActivityListener? = null + + fun setListener(context: Context) { + if (context is MainActivityListener) { + intfc = context + } + } + + fun callSetupMain(view: View, savedInstanceState: Bundle?) { + intfc?.setupMain(view, savedInstanceState) + } + + fun callOnSignIn(view: View, wantDelete: Boolean, dialog: Dialog?) { + intfc?.onSignIn(view, wantDelete, dialog) + } + fun quit(context: Context) { // Create an Intent to launch the home screen val homeIntent = Intent(Intent.ACTION_MAIN) @@ -65,43 +91,159 @@ class MainActivity : AppCompatActivity() { } } + override fun setupMain(view: View, savedInstanceState: Bundle?) { + toolbar = view.findViewById(R.id.toolbar) + + if ( + Util.fileToString(Vars.userDataFilePath) != "" && + JSONObject(Util.fileToString(Vars.userDataFilePath)).has("fullName") + ) { + toolbar.text = + JSONObject(Util.fileToString(Vars.userDataFilePath)).getString("fullName") + + role + } else { + toolbar.text = "Přihlašte se, prosím." + } + + navbar = findViewById(R.id.bottomNav) + + var frag = supportFragmentManager.findFragmentById(R.id.fragmentContainer) + + if (!Vars.resetNav) { + if (frag == null || frag is CalWeekFragment) { + frag = CalWeekFragment() + navbar.selectedItemId = R.id.nav_timetable + } + if (frag is MarksFragment) { + frag = MarksFragment() + navbar.selectedItemId = R.id.nav_marks + } + if (frag is MenuFragment) { + frag = MenuFragment() + navbar.selectedItemId = R.id.nav_menu + } + } else { + frag = CalWeekFragment() + navbar.selectedItemId = R.id.nav_timetable + Vars.resetNav = false + } + + supportFragmentManager.beginTransaction() + .replace(R.id.fragmentContainer, frag) + .commit() + + navbar.setOnItemSelectedListener { + val ft = supportFragmentManager.beginTransaction() + + when (it.itemId) { + R.id.nav_timetable -> { + ft.setCustomAnimations( + R.anim.slide_up, + R.anim.slide_down, + ) + ft.replace(R.id.fragmentContainer, CalWeekFragment()) + } + + R.id.nav_marks -> { + ft.setCustomAnimations( + R.anim.slide_up, + R.anim.slide_down, + ) + ft.replace(R.id.fragmentContainer, MarksFragment()) + } + + R.id.nav_menu -> { + ft.setCustomAnimations( + R.anim.slide_up, + R.anim.slide_down, + ) + ft.replace(R.id.fragmentContainer, MenuFragment()) + } + + else -> return@setOnItemSelectedListener false + } + + ft.commit() + true + } + + setOnInternetAvailListener() + done = true + } + // @section SIGN_IN - private fun onSignIn() { + override fun onSignIn(view: View, wantDelete: Boolean, dialog: Dialog?) { val acc = Account( - findViewById(R.id.usernameField).text.toString(), - findViewById(R.id.passwordField).text.toString(), + view.findViewById(R.id.usernameField).text.toString(), + view.findViewById(R.id.passwordField).text.toString(), 0 ) - showSplashScreen() + var checked = false + lifecycleScope.launch { + val btn = view.findViewById