diff --git a/PowerOf10/.idea/deploymentTargetDropDown.xml b/PowerOf10/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..294103b --- /dev/null +++ b/PowerOf10/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PowerOf10/.idea/gradle.xml b/PowerOf10/.idea/gradle.xml index 526b4c2..a0de2a1 100644 --- a/PowerOf10/.idea/gradle.xml +++ b/PowerOf10/.idea/gradle.xml @@ -7,13 +7,13 @@ - diff --git a/PowerOf10/.idea/misc.xml b/PowerOf10/.idea/misc.xml index 6dc2f20..72b97aa 100644 --- a/PowerOf10/.idea/misc.xml +++ b/PowerOf10/.idea/misc.xml @@ -11,7 +11,7 @@ - + diff --git a/PowerOf10/.idea/vcs.xml b/PowerOf10/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/PowerOf10/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PowerOf10/app/build.gradle b/PowerOf10/app/build.gradle index 252282f..b2951e9 100644 --- a/PowerOf10/app/build.gradle +++ b/PowerOf10/app/build.gradle @@ -3,13 +3,14 @@ plugins { id 'org.jetbrains.kotlin.android' } +apply plugin: 'kotlin-android' android { - compileSdk 32 + compileSdk 33 defaultConfig { - minSdk 23 - targetSdk 32 + minSdk 28 + targetSdk 33 versionCode 1 versionName "1.0" @@ -44,35 +45,42 @@ android { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } + namespace 'com.example.powerof10' } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1") implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" - implementation 'com.google.android.material:material:1.5.0' - implementation("androidx.compose.material3:material3:1.0.0-alpha13") - implementation("androidx.compose.material3:material3-window-size-class:1.0.0-alpha13") + implementation 'com.google.android.material:material:1.8.0' + implementation('androidx.compose.material3:material3:1.1.0-beta01') + implementation('androidx.compose.material3:material3-window-size-class:1.1.0-beta01') implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' - implementation 'androidx.activity:activity-compose:1.3.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.activity:activity-compose:1.7.0' implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.code.gson:gson:2.8.9' - implementation 'androidx.databinding:databinding-runtime:4.2.2' - implementation "androidx.navigation:navigation-compose:2.4.0-alpha10" + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'androidx.databinding:databinding-runtime:7.4.2' + implementation 'androidx.navigation:navigation-compose:2.6.0-alpha08' implementation 'com.github.ivanisidrowu.ktrssreader:android:v2.1.2' - implementation("io.coil-kt:coil-compose:2.1.0") + implementation('io.coil-kt:coil-compose:2.3.0') implementation 'com.prof18.rssparser:rssparser:3.1.3' - implementation 'org.jsoup:jsoup:1.10.3' + implementation 'org.jsoup:jsoup:1.15.4' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + implementation "androidx.core:core-ktx:1.9.0" + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + implementation 'com.github.devscast:validable:1.2.0' + implementation "com.google.accompanist:accompanist-webview:0.30.1" -} \ No newline at end of file +} diff --git a/PowerOf10/app/src/main/AndroidManifest.xml b/PowerOf10/app/src/main/AndroidManifest.xml index dcb7cf4..25f8dcc 100644 --- a/PowerOf10/app/src/main/AndroidManifest.xml +++ b/PowerOf10/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -11,6 +10,16 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PowerOf10"> + + diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt b/PowerOf10/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt index 459a264..1b4a9b0 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt @@ -49,7 +49,7 @@ class FeedViewHolder(itemView:View):RecyclerView.ViewHolder(itemView),View.OnCli } fun extractP(description: String):String{ val doc: Document = Jsoup.parse(description) - val p : Element = doc.select("p").first() + val p : Element = doc.select("p").first()!! return p.text() } diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/AthleteSearch.kt b/PowerOf10/app/src/main/java/com/example/powerof10/AthleteSearch.kt new file mode 100644 index 0000000..4bf3754 --- /dev/null +++ b/PowerOf10/app/src/main/java/com/example/powerof10/AthleteSearch.kt @@ -0,0 +1,319 @@ +package com.example.powerof10 + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.WindowInsetsAnimationController +import android.view.WindowInsetsController +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.example.powerof10.ui.theme.PowerOf10Theme +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.select.Elements +import tech.devscast.validable.EmailValidable +import tech.devscast.validable.delegates.validableEmail +import tech.devscast.validable.withValidable +import java.io.Serializable + +class AthleteSearch : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +suspend fun getHTML(firstName: String, lastName: String, club: String): Document { //retrieves the entire HTML page including tags using search parameters + val html= "https://www.thepowerof10.info/athletes/athleteslookup.aspx?surname=$lastName&firstname=$firstName&club=$club" + + return withContext(Dispatchers.IO) { + return@withContext Jsoup.connect(html).get() + } + +} + +fun getAthletes(doc: Document,mContext: Context): Serializable? {//serializable = any data structure. Function converts html to a list of elements + if (doc.getElementsByAttributeValueContaining("class","athleteprofilesubheader").isNotEmpty()){ //Checks if the link returned the athlete profile due to there being only 1 result + val browserIntent = Intent(//opens the browser and takes user to the link + Intent.ACTION_VIEW, + Uri.parse( + "https://www.thepowerof10.info/athletes/" + doc.getElementById("form1")?.attr("action") + ) + ) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile + mContext.startActivity(browserIntent) + //println(doc.getElementById("form1")?.attr("action")) + return "athlete found" + } + else if (doc.getElementsContainingText("Too many athletes found").isEmpty()){//if theres NOT too many records + val table = doc.getElementById("cphBody_dgAthletes") // returns the table of athlete search results (NEEDED as the nav bar has the background colour attribute) + var records = table?.getElementsByAttributeValueContaining("style","background-color:") //returns all records with a background colour within the results table(Line before) + records?.removeAt(0)//removes the heading list(sex,name,etc) + return records + } else { //otherwise return null usually because theres too many athlete records that were returned + return null + } + + + + +} + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AthleteSearch(navController: NavController) { + val powerOfTen = painterResource(id = R.drawable.powerof10) + var buttonPress by remember { mutableStateOf(0) }//updates the lazy column when the apply button is pressed + val scaffoldState = rememberScaffoldState() + val scope = rememberCoroutineScope() + var firstName by remember { mutableStateOf("") } + var lastName by remember { mutableStateOf("") } + var club by remember { mutableStateOf("") } + var athletesList by remember {mutableStateOf(Elements()) } + var context = LocalContext.current + LaunchedEffect(buttonPress) { //recomposes on button press + if (firstName != ""||lastName != ""){//if the either name fields are not empty + val html = getHTML(firstName,lastName,club)//gets html from the searched page + var athletesElements = getAthletes(html, context)//this is to check if the result is string, null or Elements() + when (athletesElements) { + null -> {//if null make a warning/toast + Toast.makeText(context,"Too many athletes found. Please change the search criteria.\n",Toast.LENGTH_SHORT).show() + } + "athlete found" -> {//if only a single athlete is returned + Toast.makeText(context,"Athlete found opening on browser",Toast.LENGTH_SHORT).show() + + } + else -> {//otherwise just display the records list as usual + athletesList = athletesElements as Elements + println(athletesList.size) + } + } + + + }else{// if both fields are empty + Toast.makeText(context,"Please enter your search values",Toast.LENGTH_SHORT).show() + } + + + + + } + Column { + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier, + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody(items = listOf( + MenuItems(1, "Home",Screen.HomepageRSS.route), + MenuItems(2, "Rankings",Screen.RankingPage.route), + MenuItems(3, "Athlete Search",Screen.AthleteSearch.route) + ), onItemClick = { navController.navigate(route =it.route) } + ) + }, content = { + + + Column( modifier = Modifier.fillMaxWidth() + ){ + + + LazyColumn(content = { + item { + Column(horizontalAlignment = Alignment.CenterHorizontally,modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "Athlete Search", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) + ) + TextField( + value = firstName, + onValueChange = { firstName = it }, + label = { Text("First Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + + TextField( + value = lastName, + onValueChange = { lastName = it }, + label = { Text("Last Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + + TextField( + value = club, + onValueChange = { club = it }, + label = { Text("Club Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + Button( + onClick = { + if (firstName.length < 3&&lastName.length<3 ){// validates to check fields are atleast 3 chars long + Toast.makeText(context,"Please enter at least 3 characters for either First or Last name",Toast.LENGTH_SHORT).show() + }else { + buttonPress += 1 //recompose view + } + }, modifier = Modifier + .padding(horizontal = 5.dp, vertical = 0.dp) + .fillMaxWidth(0.75F) + + ) { + Text("Apply") + + } + } + } + items(athletesList.size) { + + AthleteItem(it,athletesList) + + + } + }) + + } + }) + + } +} + +@Composable +fun AthleteItem(index: Int,athlete:Elements) { + val mContext = LocalContext.current + val background: Color = if (index%2==1){//alternates the colour + Color(0xFFD3D3D3) + } + else { + Color(0xFFF5F3F4) + } //alternates the background colour + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier + .background(background) + .fillMaxWidth() + .padding(0.dp, 10.dp) + .clickable { + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse( + "https://www.thepowerof10.info/athletes/" + athlete[index] + .child(7) + .child(0) + .attr("href") + ) + ) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile + mContext.startActivity(browserIntent) + } + ) { + + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier + .fillMaxWidth(0.25f) + .padding(start = 5.dp)) { + + Text(text = athlete[index].child(0).text() ,//First Name + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(1).text() ,//Last Name + maxLines = 1, + overflow = TextOverflow.Ellipsis) + } + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.5f)) { + Text(text = "Club:" ,//Athlete's club + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(6).text() ,//athletes club + maxLines = 1, + overflow = TextOverflow.Ellipsis) + + } + Column(modifier = Modifier.fillMaxWidth(0.4f)) { + Text(text = athlete[index].child(5).text() ,//Athlete's Gender + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(2).text() ,//Athletes Age Group + maxLines = 1, + overflow = TextOverflow.Ellipsis) + + + } + } +} +@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) +//@Preview(name = "PIXEL_42", device = Devices.PIXEL_4) +@Composable +fun DefaultPreview3() { + PowerOf10Theme { + AthleteSearch(rememberNavController()) + } +} \ No newline at end of file diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/HomeRSS.kt b/PowerOf10/app/src/main/java/com/example/powerof10/HomeRSS.kt index 1ca96da..3ebb71b 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/HomeRSS.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/HomeRSS.kt @@ -1,5 +1,6 @@ package com.example.powerof10 +import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.os.Bundle @@ -13,6 +14,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -30,6 +32,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.ViewModel import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import coil.compose.AsyncImage import com.example.powerof10.ui.theme.PowerOf10Theme import kotlinx.coroutines.* @@ -39,6 +42,8 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import tw.ktrssreader.Reader import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData +import java.net.URLEncoder +import java.nio.charset.StandardCharsets class HomeRSS : ComponentActivity() { @@ -77,7 +82,7 @@ class RSSLogic { //Extracts the description in the "

" tag from the RSS " tag" fun extractP(description: String?): String { val doc: Document = Jsoup.parse(description) - val p: Element = doc.select("p").first() + val p: Element = doc.select("p").first()!! return p.text() } @@ -85,7 +90,7 @@ fun extractP(description: String?): String { fun extractImg(description: String?): String { val doc: Document = Jsoup.parse(description) val img = doc.select("img").first() - return img.attr("src") + return img!!.attr("src") } // @@ -105,8 +110,10 @@ fun extractImg(description: String?): String { //@Preview(name = "PIXEL_3_XL", device = Devices.PIXEL_3_XL) //@Preview(name = "PIXEL_3A", device = Devices.PIXEL_3A) //@Preview(name = "PIXEL_3A_XL", device = Devices.PIXEL_3A_XL) -@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) -@Preview(name = "PIXEL_4_XL", device = Devices.PIXEL_4_XL) +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +//@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) +//@Preview(name = "PIXEL_4_XL", device = Devices.PIXEL_4_XL) @Composable fun DisplayRssHomepage(navController: NavController) { var rssitem by remember {// If any changes are made to this variable the screen recomposes also stores the RSS Data @@ -181,22 +188,23 @@ fun DisplayRssHomepage(navController: NavController) { NavDrawerBody(items = listOf( MenuItems(1, "Home",Screen.HomepageRSS.route), MenuItems(2, "Rankings",Screen.RankingPage.route), - MenuItems(3, "Settings",Screen.RankingPage.route) + MenuItems(3, "Athlete Search",Screen.AthleteSearch.route) ), onItemClick = { navController.navigate(route =it.route) } ) }, content = { Column() { - Text( - text = "Latest News", - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - modifier = Modifier.padding(10.dp) - ) + LazyColumn(content = { + item { Text( + text = "Latest News", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) + ) } rssitem.items?.let { it -> items(it.size) { - RssItem(it, rssitem = rssitem) + RssItem(it, rssitem = rssitem,navController) } } }) @@ -217,13 +225,16 @@ Column() { // The RSS item for the Lazy Column Composable (Recycler View) @Composable -fun RssItem(index: Int, rssitem: RssStandardChannelData) { +fun RssItem(index: Int, rssitem: RssStandardChannelData,navController: NavController) { val mContext = LocalContext.current Column(modifier = Modifier.clickable {// On click a browser intent is started to the link of the article - val browserIntent = - Intent(Intent.ACTION_VIEW, Uri.parse(rssitem.items?.get(index)?.link)) - browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - mContext.startActivity(browserIntent) +// val browserIntent = +// Intent(Intent.ACTION_VIEW, Uri.parse(rssitem.items?.get(index)?.link)) +// browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) +// mContext.startActivity(browserIntent) + val link = Uri.parse(rssitem.items?.get(index)?.link).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate(encodedUrl)//navigates to webview instead of browser }) { Box { AsyncImage( //coil library composable that loads images from the the src url on another thread diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/MainActivity.kt b/PowerOf10/app/src/main/java/com/example/powerof10/MainActivity.kt index 9fa2585..60947f6 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/MainActivity.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/MainActivity.kt @@ -1,6 +1,11 @@ package com.example.powerof10 +import android.os.Build import android.os.Bundle +import android.view.View +import android.view.WindowInsets +import android.view.WindowInsetsController +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable @@ -12,8 +17,10 @@ import com.example.powerof10.ui.theme.PowerOf10Theme class MainActivity : ComponentActivity() { lateinit var navController: NavHostController + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContent { PowerOf10Theme { diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/NavGraph.kt b/PowerOf10/app/src/main/java/com/example/powerof10/NavGraph.kt index ce7a219..4d16d10 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/NavGraph.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/NavGraph.kt @@ -1,9 +1,13 @@ package com.example.powerof10 import androidx.compose.runtime.Composable +import androidx.fragment.app.FragmentManager.BackStackEntry +import androidx.navigation.NamedNavArgument import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.navArgument @Composable @@ -29,5 +33,23 @@ fun SetupNavGraph( DisplayRankingPage(navController) } + composable( + route = Screen.AthleteSearch.route + ) { + + AthleteSearch(navController) + } + composable( + route = "{link}",//parameter for the articlewebview composable + arguments = listOf(navArgument("link"){ + type = NavType.StringType + nullable = false + }) + ) { + var id = requireNotNull(it.arguments?.getString("link").toString()) + ArticleWebView(id) + //println(id) + + } } } \ No newline at end of file diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/RSSArticle.kt b/PowerOf10/app/src/main/java/com/example/powerof10/RSSArticle.kt new file mode 100644 index 0000000..b891e7c --- /dev/null +++ b/PowerOf10/app/src/main/java/com/example/powerof10/RSSArticle.kt @@ -0,0 +1,53 @@ +package com.example.powerof10 + +import android.os.Bundle +import android.webkit.WebView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavController +import com.example.powerof10.ui.theme.PowerOf10Theme +import com.google.accompanist.web.WebView +import com.google.accompanist.web.rememberWebViewState + +class RSSArticle : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +@Composable +fun ArticleWebView( link: String) { + val state = rememberWebViewState(link) + println(link) + WebView( + state, + onCreated = { + it.settings.javaScriptEnabled = true}, + ) +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview4() { + PowerOf10Theme { + + } +} \ No newline at end of file diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/RankingPage.kt b/PowerOf10/app/src/main/java/com/example/powerof10/RankingPage.kt index c35c8d4..26c8583 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/RankingPage.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/RankingPage.kt @@ -15,6 +15,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -26,9 +27,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.example.powerof10.ui.theme.PowerOf10Theme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -106,6 +109,7 @@ fun getNames(doc: Document): Elements { cleanList.add(it) } } + println(cleanList) return cleanList } //Cleans the HTML by removing the records that are blank (with no name) @@ -113,32 +117,39 @@ fun getNames(doc: Document): Elements { -@Preview(name = "NEXUS_7_2013", device = Devices.NEXUS_7_2013) -@Preview(name = "NEXUS_5", device = Devices.NEXUS_5) -@Preview(name = "NEXUS_6", device = Devices.NEXUS_6) -@Preview(name = "NEXUS_9", device = Devices.NEXUS_9) -@Preview(name = "NEXUS_10", device = Devices.NEXUS_10) -@Preview(name = "NEXUS_5X", device = Devices.NEXUS_5X) -@Preview(name = "NEXUS_6P", device = Devices.NEXUS_6P) -@Preview(name = "PIXEL_C", device = Devices.PIXEL_C) -@Preview(name = "PIXEL", device = Devices.PIXEL) -@Preview(name = "PIXEL_XL", device = Devices.PIXEL_XL) -@Preview(name = "PIXEL_2", device = Devices.PIXEL_2) -@Preview(name = "PIXEL_2_XL", device = Devices.PIXEL_2_XL) -@Preview(name = "PIXEL_3", device = Devices.PIXEL_3) -@Preview(name = "PIXEL_3_XL", device = Devices.PIXEL_3_XL) -@Preview(name = "PIXEL_3A", device = Devices.PIXEL_3A) -@Preview(name = "PIXEL_3A_XL", device = Devices.PIXEL_3A_XL) +//@Preview(name = "NEXUS_7_2013", device = Devices.NEXUS_7_2013) +//@Preview(name = "NEXUS_5", device = Devices.NEXUS_5) +//@Preview(name = "NEXUS_6", device = Devices.NEXUS_6) +//@Preview(name = "NEXUS_9", device = Devices.NEXUS_9) +//@Preview(name = "NEXUS_10", device = Devices.NEXUS_10) +//@Preview(name = "NEXUS_5X", device = Devices.NEXUS_5X) +//@Preview(name = "NEXUS_6P", device = Devices.NEXUS_6P) +//@Preview(name = "PIXEL_C", device = Devices.PIXEL_C) +//@Preview(name = "PIXEL", device = Devices.PIXEL) +//@Preview(name = "PIXEL_XL", device = Devices.PIXEL_XL) +//@Preview(name = "PIXEL_2", device = Devices.PIXEL_2) +//@Preview(name = "PIXEL_2_XL", device = Devices.PIXEL_2_XL) +//@Preview(name = "PIXEL_3", device = Devices.PIXEL_3) +//@Preview(name = "PIXEL_3_XL", device = Devices.PIXEL_3_XL) +//@Preview(name = "PIXEL_3A", device = Devices.PIXEL_3A) +//@Preview(name = "PIXEL_3A_XL", device = Devices.PIXEL_3A_XL) -@SuppressLint("MutableCollectionMutableState") +@SuppressLint("MutableCollectionMutableState", "UnusedMaterialScaffoldPaddingParameter") @Preview(name = "PIXEL_4", device = Devices.PIXEL_4) //@Preview(name = "PIXEL_4_XL", device = Devices.PIXEL_4_XL) -@OptIn(ExperimentalMaterialApi::class) +@Composable +fun UserProfilePreview2(){ + DisplayRankingPage(navController = rememberNavController()) +} + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun DisplayRankingPage(navController: NavController) { //following observable variable descriptions apply to all var yearExpanded by remember { mutableStateOf(false) }//checks the state of the dropdown (expanded or not) - val yearOptions = listOf("2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010", "2009", "2008", "2007", "2006", "All Time")//variables inside the menu + val yearOptions = listOf("2023","2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010", "2009", "2008", "2007", "2006", "All Time")//variables inside the menu var yearSelectedOptionText by remember { mutableStateOf(yearOptions[0]) }//the current object that's selected var regionExpanded by remember { mutableStateOf(false) } @@ -240,199 +251,206 @@ fun DisplayRankingPage(navController: NavController) { NavDrawerBody(items = listOf( MenuItems(1, "Home",Screen.HomepageRSS.route), MenuItems(2, "Rankings",Screen.RankingPage.route), - MenuItems(3, "Settings",Screen.RankingPage.route) + MenuItems(3, "Athlete Search",Screen.AthleteSearch.route) ), onItemClick = { navController.navigate(route =it.route) } ) }, content = { Column(modifier = Modifier.fillMaxWidth()) { - Text( - text = "Rankings", - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - modifier = Modifier.padding(10.dp) - ) - Row( - horizontalArrangement = Arrangement.SpaceAround, - modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 10.dp) - ) { - ExposedDropdownMenuBox( - expanded = yearExpanded, - modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp), - onExpandedChange = { yearExpanded = !yearExpanded }, - ) { - TextField( - readOnly = true, - value = yearSelectedOptionText, - onValueChange = {}, - label = { Text("1. Year") }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - ExposedDropdownMenu( - expanded = yearExpanded, - onDismissRequest = { yearExpanded = false }, - ) { - yearOptions.forEach { yearSelectionOption -> - DropdownMenuItem( - onClick = { - yearSelectedOptionText = yearSelectionOption - yearExpanded = false - } - ) { Text(text = yearSelectionOption) } - } - } - } - ExposedDropdownMenuBox( - expanded = regionExpanded, - modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp), - onExpandedChange = { regionExpanded = !regionExpanded }, - ) { - TextField( - readOnly = true, - value = regionSelectedOptionText, - onValueChange = {}, - label = { Text("2. Region") }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), + + LazyColumn(content = { + item { + Text( + text = "Rankings", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) ) - ExposedDropdownMenu( - expanded = regionExpanded, - onDismissRequest = { regionExpanded = false }, + Row( + horizontalArrangement = Arrangement.SpaceAround, + modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 10.dp) ) { - regionOptions.forEach { regionSelectionOption -> - DropdownMenuItem( - onClick = { - regionSelectedOptionText = regionSelectionOption - regionExpanded = false + ExposedDropdownMenuBox( + expanded = yearExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { yearExpanded = !yearExpanded }, + ) { + TextField( + readOnly = true, + value = yearSelectedOptionText, + onValueChange = {}, + label = { Text("1. Year") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = yearExpanded, + onDismissRequest = { yearExpanded = false }, + ) { + yearOptions.forEach { yearSelectionOption -> + DropdownMenuItem( + onClick = { + yearSelectedOptionText = yearSelectionOption + yearExpanded = false + } + ) { Text(text = yearSelectionOption) } } - ) { Text(text = regionSelectionOption) } + } } - } - } - ExposedDropdownMenuBox( - expanded = genderExpanded, - modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp), - onExpandedChange = { genderExpanded = !genderExpanded }, - ) { - TextField( - readOnly = true, - value = genderSelectedOptionText, - onValueChange = { - }, - label = { Text("3. Gender") }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - ExposedDropdownMenu( - expanded = genderExpanded, - onDismissRequest = { genderExpanded = false }, - ) { - genderOptions.forEach { selectionOption -> - DropdownMenuItem( - onClick = { - genderSelectedOptionText = selectionOption - updateOptions += 1 + ExposedDropdownMenuBox( + expanded = regionExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { regionExpanded = !regionExpanded }, + ) { + TextField( + readOnly = true, + value = regionSelectedOptionText, + onValueChange = {}, + label = { Text("2. Region") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = regionExpanded, + onDismissRequest = { regionExpanded = false }, + ) { + regionOptions.forEach { regionSelectionOption -> + DropdownMenuItem( + onClick = { + regionSelectedOptionText = regionSelectionOption + regionExpanded = false + } + ) { Text(text = regionSelectionOption) } + } + } + } - genderExpanded = false + ExposedDropdownMenuBox( + expanded = genderExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { genderExpanded = !genderExpanded }, + ) { + TextField( + readOnly = true, + value = genderSelectedOptionText, + onValueChange = { + }, + label = { Text("3. Gender") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = genderExpanded, + onDismissRequest = { genderExpanded = false }, + ) { + genderOptions.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + genderSelectedOptionText = selectionOption + groupSelectedOptionText="Overall" + eventSelectedOptionText="60" + updateOptions += 1 + + + genderExpanded = false + } + ) { Text(text = selectionOption) } } - ) { Text(text = selectionOption) } + } } } - } - } - Row( - horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier - .fillMaxWidth() - .padding(0.dp, 0.dp, 0.dp, 10.dp) - ) { - ExposedDropdownMenuBox( - expanded = groupExpanded, - modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp), - onExpandedChange = { groupExpanded = !groupExpanded }, - ) { - TextField( - readOnly = true, - value = groupSelectedOptionText, - onValueChange = {}, - label = { Text("4. Group") }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - ExposedDropdownMenu( - expanded = groupExpanded, - onDismissRequest = { groupExpanded = false }, + Row( + horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 0.dp, 0.dp, 10.dp) ) { - groupOptions.forEach { selectionOption -> - DropdownMenuItem( - onClick = { - groupSelectedOptionText = selectionOption - updateOptions += 1 - groupExpanded = false + ExposedDropdownMenuBox( + expanded = groupExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { groupExpanded = !groupExpanded }, + ) { + TextField( + readOnly = true, + value = groupSelectedOptionText, + onValueChange = {}, + label = { Text("4. Group") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = groupExpanded, + onDismissRequest = { groupExpanded = false }, + ) { + groupOptions.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + groupSelectedOptionText = selectionOption + eventSelectedOptionText="60" + updateOptions += 1 + groupExpanded = false + } + ) { Text(text = selectionOption) } } - ) { Text(text = selectionOption) } + } } - } - } - ExposedDropdownMenuBox( - expanded = eventExpanded, - modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp), - onExpandedChange = { eventExpanded = !eventExpanded }, - ) { - TextField( - readOnly = true, - value = eventSelectedOptionText, - onValueChange = {}, - label = { Text("5. Event") }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - ExposedDropdownMenu( - expanded = eventExpanded, - onDismissRequest = { eventExpanded = false }, - ) { - subList.forEach { selectionOption -> - DropdownMenuItem( - onClick = { - eventSelectedOptionText = selectionOption - updateOptions += 1 - eventExpanded = false + ExposedDropdownMenuBox( + expanded = eventExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { eventExpanded = !eventExpanded }, + ) { + TextField( + readOnly = true, + value = eventSelectedOptionText, + onValueChange = {}, + label = { Text("5. Event") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = eventExpanded, + onDismissRequest = { eventExpanded = false }, + ) { + subList.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + eventSelectedOptionText = selectionOption + updateOptions += 1 + eventExpanded = false + } + ) { Text(text = selectionOption) } } - ) { Text(text = selectionOption) } + } } - } - } - Button( - onClick = { buttonPress += 1 }, modifier = Modifier - .weight(1f) - .padding(horizontal = 10.dp, vertical = 0.dp) - ) { - Text("Apply") - - } + Button( + onClick = { buttonPress += 1 }, modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp) + ) { + Text("Apply") - } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = "Rank") - Text(text = "Club") - Text(text = "Performance") - } + } - LazyColumn(content = { + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Rank") + Text(text = "Club") + Text(text = "Performance") + } + } items(athletesList.size) { @@ -463,9 +481,19 @@ fun RankingItem(index: Int, athleteList: Elements){ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .background(background) .fillMaxWidth() - .clickable { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.thepowerof10.info/"+athleteList[index].child(6).child(0).attr("href"))) + .clickable { + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse( + "https://www.thepowerof10.info/" + athleteList[index] + .child(6) + .child(0) + .attr("href") + ) + ) browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile - mContext.startActivity(browserIntent) }) { + mContext.startActivity(browserIntent) + }) { Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.08f)) { Text(text = rank ) } diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/Screen.kt b/PowerOf10/app/src/main/java/com/example/powerof10/Screen.kt index 8c69ef6..4427e07 100644 --- a/PowerOf10/app/src/main/java/com/example/powerof10/Screen.kt +++ b/PowerOf10/app/src/main/java/com/example/powerof10/Screen.kt @@ -4,4 +4,6 @@ sealed class Screen(val route: String) { object Welcome: Screen(route ="welcome" ) object HomepageRSS: Screen(route = "Homepage_rss") object RankingPage: Screen(route = "RankingPage") + object AthleteSearch: Screen(route = "AthleteSearch") + object RSSArticle: Screen(route = "RSSArticle") } diff --git a/PowerOf10/app/src/main/res/values/colors.xml b/PowerOf10/app/src/main/res/values/colors.xml index f8c6127..ddaea49 100644 --- a/PowerOf10/app/src/main/res/values/colors.xml +++ b/PowerOf10/app/src/main/res/values/colors.xml @@ -1,6 +1,7 @@ #FFBB86FC + #FFE5383B #FF6200EE #FF3700B3 #FF03DAC5 diff --git a/PowerOf10/app/src/main/res/values/strings.xml b/PowerOf10/app/src/main/res/values/strings.xml index 7ca9ea1..7ca6435 100644 --- a/PowerOf10/app/src/main/res/values/strings.xml +++ b/PowerOf10/app/src/main/res/values/strings.xml @@ -6,4 +6,6 @@ RSSFEED RankingPage NavDrawer + AthleteSearch + RSSArticle \ No newline at end of file diff --git a/PowerOf10/app/src/main/res/values/themes.xml b/PowerOf10/app/src/main/res/values/themes.xml index 2917b7d..e990fa0 100644 --- a/PowerOf10/app/src/main/res/values/themes.xml +++ b/PowerOf10/app/src/main/res/values/themes.xml @@ -2,6 +2,7 @@ \ No newline at end of file diff --git a/PowerOf10/build.gradle b/PowerOf10/build.gradle index 46b64bf..f165281 100644 --- a/PowerOf10/build.gradle +++ b/PowerOf10/build.gradle @@ -1,14 +1,22 @@ buildscript { + ext.kotlin_version = '1.8.0' ext { - compose_version = '1.1.1' - navigationVersion = "2.3.0" + compose_version = '1.4.0' + navigationVersion = '2.5.3' } + repositories { + mavenCentral() + maven { url 'https://jitpack.io' } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.3' apply false - id 'com.android.library' version '7.1.3' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false + id 'org.jetbrains.kotlin.android' version '1.6.21' apply false } task clean(type: Delete) { diff --git a/PowerOf10/gradle/wrapper/gradle-wrapper.properties b/PowerOf10/gradle/wrapper/gradle-wrapper.properties index 1c18334..5cd84b2 100644 --- a/PowerOf10/gradle/wrapper/gradle-wrapper.properties +++ b/PowerOf10/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon May 23 03:31:55 BST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME